package mapping.data;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
import java.util.Hashtable;

/**
 * This is the table of all the jobs of a Mapper. Any access to any Job in this
 * table have to be made through the setters and getters of this class. This
 * class is thread safe. This class notifies a JobsTableListener Class when the
 * table is changed.
 * 
 */

public class JobsTable extends Hashtable<Long, Job> {
	
	/**
	 * The serialVersionUID is used to universally identify this version of this
	 * class.
	 */
	private static final long serialVersionUID = -3000311390159709455L;
	
	/**
	 * Job ID counter
	 */
	private long jobID = 1;
	
	/**
	 * The listener objects that handle events
	 */
	private JobsTableListener listener;

	
	/**
	 * Default constructor
	 * 
	 * @param initialCapacity
	 */
	public JobsTable(int initialCapacity) {
		super(initialCapacity);
	}

	/**
	 * This method add a job Job to this table.
	 * 
	 * @param job the job to be added.
	 */
	public void addJob(Job job) {
		job.setIndex(jobID++);
		job.setTimeSubmitted(new GregorianCalendar());
		this.put(job.getIndex(), job);
		if (this.listener != null)
			this.listener.jobHasBeenAdded(job.getIndex(), job.getSecondaryID(), job.getJobClassID(), "",
					job.getTimeSubmitted(), job.getTimeSent(), job.getTimeStarted(),
					job.getTimeDone(),
					job.getStatus(), job.isTimedOut());

	}

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

	// Getters for Job Class
	public boolean isJobTimedOut(long l) {
		return this.get(l).isTimedOut();
	}
	
	public String getJobHostName(long l) {
		return this.get(l).getServer().getHostName();
	}
	
	public long getJobIndex(long l) {
		return this.get(l).getIndex();
	}

	public long getJobSecondaryID(long l) {
		return this.get(l).getSecondaryID();
	}

	public int getJobClassID(long l) {
		return this.get(l).getJobClassID();
	}
	
	public long getJobexecutionTime(long l) {
		return this.get(l).getexecutionTime();
	}
	
	public Server getJobServer(long l) {
		return this.get(l).getServer();
	}

	public String getJobStatus(long l) {
		return this.get(l).getStatus();
	}

	public GregorianCalendar getJobTimeSubmitted(long l) {
		return this.get(l).getTimeSubmitted();
	}

	public GregorianCalendar getJobTimeSent(long l) {
		return this.get(l).getTimeSent();
	}

	public GregorianCalendar getJobTimeStarted(long l) {
		return this.get(l).getTimeStarted();
	}

	public GregorianCalendar getJobTimeDone(long l) {
		return this.get(l).getTimeDone();
	}

	public boolean getJobIsTimedOut(long l) {
		return this.get(l).isTimedOut();
	}

	// Setters for Job

	public void setJobSecondaryID(long id, long secondID) {
		this.get(id).setSecondaryID(secondID);
		if (this.listener != null)
			this.listener.secondaryIDHasBeenChanged(id, secondID);
	}

	public void setJobClassID(long id, int classID) {
		this.get(id).setJobClassID(classID);
		if (this.listener != null)
			this.listener.jobClassIDHasBeenChanged(id, classID);
	}

	public void setJobServer(long id, Server server) {
		this.get(id).setServer(server);
		if (this.listener != null) {
			if (server != null) {
				this.listener.serverNameHasBeenChanged(id, server.getHostName());
				this.listener.serverIDHasBeenChanged(id, server.getIndex());
			}
			else {
				this.listener.serverNameHasBeenChanged(id, "");
				this.listener.serverIDHasBeenChanged(id, -1);
			}
		}
	}

	public void setJobTimeSubmitted(long id, GregorianCalendar cal) {
		this.get(id).setTimeSubmitted(cal);
		if (this.listener != null)
			this.listener.timeSubmittedHasBeenChanged(id, cal);
	}

	public void setJobTimeSent(long id, GregorianCalendar cal) {
		this.get(id).setTimeSent(cal);
		if (this.listener != null)
			this.listener.timeSentHasBeenChanged(id, cal);
		this.listener.statusHasBeenChanged(id, this.getJobStatus(id));
	}

	public void setJobTimeStarted(long id, GregorianCalendar cal) {
		this.get(id).setTimeStarted(cal);
		if (this.listener != null) {
			this.listener.timeStartedHasBeenChanged(id, cal);
			this.listener.statusHasBeenChanged(id, this.getJobStatus(id));
		}
	}

	public void setJobTimeDone(long id, GregorianCalendar cal) {
		this.get(id).setTimeDone(cal);
		if (this.listener != null) {
			this.listener.timeDoneHasBeenChanged(id, cal);
			this.listener.statusHasBeenChanged(id, this.getJobStatus(id));
		}
	}

	public void setTimedOut(long id, boolean timedOut) {
		this.get(id).setTimedOut(timedOut);
		if (this.listener != null) {
			this.listener.timedOutStatusChanged(id, timedOut);
			this.listener.statusHasBeenChanged(id, Job.TIMEDOUT);
		}
	}

	public void addJobsTableListener(JobsTableListener listener) {
		this.listener = listener;
	}

	public static int CLOCK_DIFFERENCE_TOLARNCE = 3;

	/**
	 * returns The mean of the differences between Time Submitted and Time Done
	 * for all jobs excluding timed out jobs.
	 */
	public double getResponseTimeInSeconds() {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null
				continue;

			if (this.get(i).isTimedOut()) // skip if timed-out
				continue;

			time = this.get(i).getTimeBetweenSubmissionTimeAndDoneTimeInSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE) { // Job is done, 3
				// seconds of
				// difference
				// between clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Submitted and Time Done
	 * including timed out jobs.
	 */
	public double getResponseTimeInSecondsIncludingTimedOut() {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null
				continue;

			time = this.get(i).getTimeBetweenSubmissionTimeAndDoneTimeInSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE) { // Job is done, 3
				// seconds of
				// difference
				// between clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Sent and Time Submitted
	 * excluding timed out jobs.
	 */
	public double getSchedulingDelayInMilliSeconds() {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null
				continue;

			if (this.get(i).isTimedOut()) // skip if timed-out
				continue;

			time = this.get(i).getTimeBetweenSubmissionTimeAndSentTimeInMilliSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE * 1000) { // Job is
				// done, 3
				// seconds
				// of
				// difference
				// between
				// clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Sent and Time Submitted
	 * including timed out jobs.
	 */
	public double getSchedulingDelayInMilliSecondsIncludingTimedOut() {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null
				continue;

			time = this.get(i).getTimeBetweenSubmissionTimeAndSentTimeInMilliSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE * 1000) { // Job is
				// done, 3
				// seconds
				// of
				// difference
				// between
				// clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}

		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Started and Time Sent
	 * excluding timed out jobs.
	 */
	public double getCommunicationDelayInSeconds() {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null
				continue;
			if (this.get(i).isTimedOut()) // skip if timed-out
				continue;

			time = this.get(i).getTimeBetweenStartTimeAndSentTimeInSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE) { // Job is done, 3
				// seconds of
				// difference
				// between clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Started and Time Sent
	 * excluding timed out jobs.
	 */
	public double getCommunicationDelayInSecondsIncludingTimedOut() {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null
				continue;

			time = this.get(i).getTimeBetweenStartTimeAndSentTimeInSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE) { // Job is done, 3
				// seconds of
				// difference
				// between clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Submitted and Time Done
	 * for the job class with ID jobClassID excluding timed out jobs.
	 */
	public double getResponseTimeInSeconds(int jobClassID) {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null skip
				continue;

			if (this.get(i).isTimedOut()) // skip if timed-out
				continue;

			if (this.get(i).getJobClassID() != jobClassID) // if not job class
				// desired, then
				// skip
				continue;

			time = this.get(i).getTimeBetweenSubmissionTimeAndDoneTimeInSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE) { // Job is done, 3
				// seconds of
				// difference
				// between clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Submitted and Time Done
	 * for the job class with ID class jobClassID including timed-out jobs
	 */
	public double getResponseTimeInSecondsIncludingTimedOut(int jobClassID) {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null skip
				continue;

			if (this.get(i).getJobClassID() != jobClassID) // if not job class
				// desired, then
				// skip
				continue;

			time = this.get(i).getTimeBetweenSubmissionTimeAndDoneTimeInSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE) { // Job is done, 3
				// seconds of
				// difference
				// between clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Sent and Time Submitted
	 * for the job class with ID jobClassID excluding timed out jobs. 
	 */
	public double getSchedulingDelayInMilliSeconds(int jobClassID) {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null
				continue;

			if (this.get(i).isTimedOut()) // skip if timed-out
				continue;

			if (this.get(i).getJobClassID() != jobClassID) // if not job class
				// desired, then
				// skip
				continue;

			time = this.get(i).getTimeBetweenSubmissionTimeAndSentTimeInMilliSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE * 1000) { // Job is
				// done, 3
				// seconds
				// of
				// difference
				// between
				// clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Sent and Time Submitted
	 * for the class jobClassID including timed out jobs.
	 */
	public double getSchedulingDelayInMilliSecondsIncludingTimedOut(int jobClassID) {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null
				continue;

			if (this.get(i).getJobClassID() != jobClassID) // if not job class
				// desired, then
				// skip
				continue;

			time = this.get(i).getTimeBetweenSubmissionTimeAndSentTimeInMilliSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE * 1000) { // Job is
				// done, 3
				// seconds
				// of
				// difference
				// between
				// clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Started and Time Sent
	 * for the class jobClassID excluding timed out jobs.
	 */
	public double getCommunicationDelayInSeconds(int jobClassID) {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null
				continue;
			if (this.get(i).isTimedOut()) // skip if timed-out
				continue;
			if (this.get(i).getJobClassID() != jobClassID) // if not job class
				// desired, then
				// skip
				continue;

			time = this.get(i).getTimeBetweenStartTimeAndSentTimeInSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE) { // Job is done, 3
				// seconds of
				// difference
				// between clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	/**
	 * returns The mean of the differences between Time Started and Time Sent
	 * for the class jobClassID including timed out jobs.
	 */
	public double getCommunicationDelayInSecondsIncludingTimedOut(int jobClassID) {
		double sum = 0;
		double count = 0;

		double time;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i) == null) // in case of null
				continue;
			if (this.get(i).getJobClassID() != jobClassID) // if not job class
				// desired, then
				// skip
				continue;

			time = this.get(i).getTimeBetweenStartTimeAndSentTimeInSeconds();
			if (time >= -1 * CLOCK_DIFFERENCE_TOLARNCE) { // Job is done, 3
				// seconds of
				// difference
				// between clocks
				if (time < 0)
					time = 0;
				sum += time;
				count++;
			} // else do nothing here ( i.e just increment index i)
		}
		return (sum / count);
	}

	public void saveToFile(String fileName) {
		GregorianCalendar dateStarted, dateSent, dateDone;
		String dateStartedString, dateSentString, dateDoneString, timedOut;
		SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
		String hostname = "";
		String serverID = "";
		
		
		
		BufferedWriter writer = null;
		try {
			writer = new BufferedWriter(new FileWriter(fileName));

			writer.append("Job ID\t" + "Secondary ID\t" + "Job Class ID\t" + "Server\t" + "Server ID\t" + "Date Submitted\t"
					+ "Date Sent\t" + "Date Started\t" + "Date Done\t" + "Job Status\t" + "T/O\n"

			);

		} catch (IOException e1) {
			e1.printStackTrace();
		}

		for (long i = 0; i < this.size(); i++) {
			dateSent = this.getJobTimeSent(i + 1);
			if (dateSent == null) {
				dateSentString = "";
			} else
				dateSentString = formatter.format(dateSent.getTime());

			dateStarted = this.getJobTimeStarted(i + 1);
			if (dateStarted == null) {
				dateStartedString = "";
			} else
				dateStartedString = formatter.format(dateStarted.getTime());

			dateDone = this.getJobTimeDone(i + 1);
			if (dateDone == null) {
				dateDoneString = "";
			} else
				dateDoneString = formatter.format(dateDone.getTime());

			if (this.getJobIsTimedOut(i + 1)) {
				timedOut = "Yes";
			} else
				timedOut = "No";
			hostname ="";
			serverID = "";
			try {
				hostname =  this.getJobServer(i + 1).getHostName();
				serverID = this.getJobServer(i + 1).getIndex()+"";
			} catch (NullPointerException ex ){
				
			}
			
			try {
				writer.append(this.getJobIndex(i + 1) + "\t" + this.getJobSecondaryID(i + 1) + "\t" + this.getJobClassID(i + 1)  +"\t"
						+ hostname + "\t" + serverID + "\t"
						+ formatter.format(this.getJobTimeSubmitted(i + 1).getTime()) + "\t" + dateSentString + "\t"
						+ dateStartedString + "\t" + dateDoneString + "\t" + this.getJobStatus(i + 1) + "\t" + timedOut
						+ "\n");

			} catch (IOException e) {
				e.printStackTrace();
			}
	
		}
		if (writer != null)
			try {
				writer.flush();
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
	}
	
	/**
	 * @param jobClassID the id of the job class
	 * @return number of jobs belonging to that job class that has timed out.
	 */
	public int getTimeOutedJobsCount(int jobClassID) {
		int count = 0;
		for (long i = 1; i <= this.size(); i++) {
			if (this.get(i).getJobClassID() != jobClassID) // if not job class
				continue;
			if (this.get(i).isTimedOut())
				count++;
		}
		return count;
	}
	
	/**
	 * @return number of jobs that has timed out.
	 */
	public int getTimeOutedJobsCount() {
		int count = 0;
		for (long i = 0; i < this.size(); i++) {
			if (this.get(i).isTimedOut())
				count++;
		}
		return count;
	}
	
	
	public static void main(String[] args) {
		SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
		GregorianCalendar cal = new GregorianCalendar();
		System.out.print(formatter.format(cal.getTime()));
	}

}
