package generating;

import java.util.GregorianCalendar;

import arrival_generator.ArrivalGenerator;
import arrival_generator.EXP_AND_UNI_AG;
import logging.Logger;
import mapping.Mapper;
import mapping.data.Job;
import mapping.data.JobClass;

/**
 * A Thread which is responsible for generating 
 * all the jobs for a specific job class
 * 
 * @author Majd Kokaly
 * 
 */

public class JobsGenerator implements Runnable {


	/** JobClass Every Job Generator has one jobClass */
	private JobClass jobClass;

	/** Mapper is the main mapper in the system */
	private Mapper mapper;

	/** Boolean member to control the life of the thread */
	private boolean alive = true;
	
	/** The number of operations generated by this thread */
	private int jobsGenerated = 0;

	/**
	 * The total of sleep Time. This used with jobsGenerated to calculate the
	 * actual rate
	 */
	private long sumOfSleepTime = 0;
	
	/**
	 * The total of jobs' execution times. This used with jobsGenerated to calculate the
	 * actual execution time mean
	 */
	private long sumOfExecutionTime = 0;
	
	/**
	 * The array generated by the arrival generator. The format is
	 * JobClass ID, Arrival Time, Execution Time
	 */
	private long[] JobInfo;
	
	/**  TimeStamp that stores the date that the generation process began */
	private GregorianCalendar startTime;
	
	/**
	 * The Active Arrival Generator 
	 */
	private ArrivalGenerator arrivalgenerator;
	
	/** A logger object responsible for logging events */
	private Logger logger;
	
	/**
	 * The main constructor
	 * 
	 * @param jobClass
	 *            is the job class that this generator is responsible for
	 *            generating.
	 */
	
	
	public JobsGenerator(JobClass jobClass, Mapper mapper) {
		
		this.setJobClass(jobClass);
		this.setMapper(mapper);
		//Sets the Arrival Generator to whatever method of job Generation is desired
		//Exponential and Uniform Generator used as default
		this.setArrivalGenerator(new EXP_AND_UNI_AG(jobClass, mapper));
		this.setLogger(new Logger("generating"));
		
	}
	
	protected long[] getJobInfo()
	{
		return this.JobInfo;
	}
	
	protected void setNextJobInfo()
	{
		this.JobInfo = this.getArrivalGenerator().getNextJobInfo();
	}
	
	protected JobClass getJobClass() {
		return jobClass;
	}

	protected void setJobClass(JobClass jobClass) {
		this.jobClass = jobClass;
	}

	public Mapper getMapper() {
		return mapper;
	}

	public void setMapper(Mapper mapper) {
		this.mapper = mapper;
	}
	
	public ArrivalGenerator getArrivalGenerator()
	{
		return arrivalgenerator;
	}
	
	public void setArrivalGenerator(ArrivalGenerator arrivalGenerator)
	{
		arrivalgenerator = arrivalGenerator;
	}
	
	public synchronized boolean isAlive() {
		return alive;
	}

	public synchronized void setAlive(boolean alive) {
		this.alive = alive;
	}

	private int getJobsGenerated() {
		return jobsGenerated;
	}

	private long getSumOfSleepTime() {
		return sumOfSleepTime;
	}

	
	public synchronized double getActualRate() {
		double timeUnitsSinceStart = ((new GregorianCalendar()).getTimeInMillis() - this.getStartTime().getTimeInMillis())/ ( 1000 * 60 * this.getMapper().getTimeUnitInMinutes() );
		
		System.out.print( "difference:" + ( ((double)  this.jobsGenerated) / ( timeUnitsSinceStart ) - (((double) this.getJobsGenerated()) / ((double) this.getSumOfSleepTime() / (1000 * 60 * this.getMapper()
				.getTimeUnitInMinutes()))))  );
		
		return ((double)  this.jobsGenerated) / ( timeUnitsSinceStart );
		
		
	}

	public synchronized double getActualAverage() {
		return (((double) this.getSumOfSleepTime() / (1000 * 60 * this.getMapper().getTimeUnitInMinutes())) / ((double) this
				.getJobsGenerated()));
	}
	
	public double getAcutualIterationAverage() {
		return this.getSumOfExecutionTime() / (double) this.getJobsGenerated();
	}

	public synchronized long getSumOfExecutionTime() {
		return sumOfExecutionTime;
	}

	public synchronized void setSumOfExecutionTime(long sumOfExecutionTime) {
		this.sumOfExecutionTime = sumOfExecutionTime;
	}
	
	private GregorianCalendar getStartTime() {
		return startTime;
	}

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



	public Logger getLogger() {
		return logger;
	}

	public void setLogger(Logger logger) {
		this.logger = logger;
	}

	public synchronized int getJobsGeneratedNumber() {
		return this.jobsGenerated;
	}
	
	/**
	 * For statistical purposes this records the event of a generation
	 * 
	 * @param sleepTime the sleepTime before this generation
	 * @param iteration the executionTime of the job generated
	 */
	private synchronized void recordGeneration(long sleepTime, long executionTime) {
		this.jobsGenerated++;
		this.sumOfSleepTime += sleepTime;
		this.sumOfExecutionTime += executionTime;
	}

	/**
	 * 
	 * @return the execution that follows the class execution time but may vary
	 *        a little if a probability distribution is being used
	 */
	private long getNextExecutionTime() {
		return this.getJobInfo()[2];
	}
	
	/**
	 * This method creates a job and sends it to the mapper
	 */
	private long generateJob() {
		
		// Find a suitable execution time depending on job class
		long executionTime = getNextExecutionTime();
		
		// Create a job and stamp it with the appropriate job class.
		Job job = new Job((int)this.getJobInfo()[0], executionTime);
		
		// Send the job to the mapper
		this.getMapper().submitJob(job);
		
		// Logging
		
		this.getLogger().log("Task generated from class: " + (int)this.getJobInfo()[0] + "\texecutionTime: " + executionTime);
		
		return executionTime;
	}

	/**
	 * The run method of the thread
	 * This thread generates a job, then sleeps and then wakes up 
	 * to generate a job and then sleeps again and so forth.
	 */
	public void run() {
		long executionTime;
		setStartTime(new GregorianCalendar());
		while (this.isAlive()) {
			
			this.setNextJobInfo();
			
			try 
			{
				long sleepTime = this.getJobInfo()[1];
				
				Thread.sleep(sleepTime);
				
				// Kill the thread if it is not alive
				if (!this.isAlive()) 
				{
					this.getArrivalGenerator().closeLogger();
					return;
				}
				// Generate a task
				executionTime = generateJob();
				recordGeneration(sleepTime , executionTime);
				
				
			} 
			catch (InterruptedException e) 
			{
				if (!isAlive())
				{
					this.getArrivalGenerator().closeLogger();
					return;
				}
				e.printStackTrace();
			}
		}
	}

}
