package mapping;

import mapping.data.Job;
import java.util.*;

/**
 * This is an implementation of the SLQ scheduling policy
 * 
 * @author Ben Kybartas & Majd Kokaly
 */
public class SLQ_MS extends MappingScheme 
{
	/**
	 * The serialVersionUID is used to universally identify this version of this
	 * class.
	 */
	private static final long serialVersionUID = 637564873568473568L;
	
	/**
	 * The helper thread
	 */
	private SLQ_MS_Thread slq_ms_thread;
	
	/**
	 * An array of jobs queue. Every job class has is own queue 
	 */
	private JobsQueue[] classesQueues;
	
	/**
	 * This is the integer sent to the helper when there are no jobs in the queue
	 */
	final static int NO_JOBS = -1;
	
	/**
	 * A default constructor.
	 * 
	 *  @param mapper
	 *  		The mapper object
	 */
	public SLQ_MS(Mapper mapper) 
	{
		super(mapper);
		this.initQueues();
		this.setDescription("SLQ");
	}
	
	protected SLQ_MS_Thread getSLQ_MS_Thread()
	{
		return slq_ms_thread;
	}
	
	protected void setSLQ_MS_Thread (SLQ_MS_Thread Slq_ms_thread)
	{
		this.slq_ms_thread = Slq_ms_thread;
	}
	
	public JobsQueue[] getClassesQueues() 
	{
		return classesQueues;
	}
	
	public void setClassesQueue(JobsQueue[] ClassesQueues )
	{
		this.classesQueues = ClassesQueues;
	}
	
	/**
	 * 
	 * @see mapping.MappingScheme#startMappingScheme()
	 */
	public void startMappingScheme()
	{
		setSLQ_MS_Thread(new SLQ_MS_Thread(this));
		//Starting the thread
		this.getSLQ_MS_Thread().startThread();
	}
	
	/**
	 * 
	 * @see mapping.MappingScheme#stopMappingScheme()
	 */
	public void stopMappingScheme()
	{
		//Stopping the thread
		this.getSLQ_MS_Thread().stopThread();
	}
	
	protected void mapJob(Job job)
	{
		/**
		 * SQL maintains a queue for every class of jobs. This line stores a
		 * specfic job in its own class
		 */
		int jobClassID = (int) this.getMapper().getJobsTable().getJobClassID(job.getIndex());
		this.getClassesQueues()[jobClassID-1].enqueue(job);
	}
	
	/**
	 * Resubmit timed out jobs
	 * 
	 * @see mapping.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 mapping.MappingScheme#serverIsDown(int)
	 */
	public void serverIsDown(int serverID)
	{
		
	}
	
	/**
	 * 
	 * @see mapping.MappingScheme#serverIsUp(int)
	 */
	public void serverIsUp(int serverID)
	{
		
	}
	
	/**
	 * 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 SLQ_MS_Thread. This method has the logic
	 * this scheduling policy. In this method, a job is sought to assign to a server
	 * with readerServer.
	 * 
	 * @param readyServer
	 * 			the id of the available server 
	 */
	protected int sendJobForServer(int serverID)
	{
		
		int queueIndex = NO_JOBS; 
		int size = 0;
		Job job = null;
		
		for (int i = 0; i < this.getClassesQueues().length; i++)
		{
			if (this.getClassesQueues()[i].size() > 0)
			{
				if (this.getClassesQueues()[i].size() > size)
				{
					//The longest queue will be serviced by the available server
					size = this.getClassesQueues()[i].size();
					queueIndex = i;
				}
				else if (this.getClassesQueues()[i].size() == size)
				{
					//Ties in class size are broken arbitrarily
					int tiebreak = 1 + ( new Random().nextInt(2));
					
					if (tiebreak == 2)
					{
						size = this.getClassesQueues()[i].size();
						queueIndex = i;
					}
				}
			}
		}

		//Returns job from longest queue, if there exists a longest queue.
		if (queueIndex > NO_JOBS)
		{
			job = this.getClassesQueues()[queueIndex].peak();
		}
		
		//Attempts to send the job, if the job exists
		if (job != null)
		{
			long result = this.getMapper().sendJob(job.getIndex(), serverID);
			if (result >= 0)
			{
				//Job could be sent
				this.getClassesQueues()[queueIndex].dequeue();
			}
			else
			{
				job = null;
			}
		}
		return -1;
	}
}
