package mapping;

import java.util.GregorianCalendar;

import logging.Logger;

/**
 * 
 * This thread just wakes up every System time unit and resolves the delta star
 * matrix
 * 
 * @author Majd Kokaly
 * 
 */
public class LPAS_DG_MS_SOLVING_TICKER implements Runnable {

	/**
	 * This flag tells the thread if it is alive or should kill itself.
	 */
	private boolean alive = true;

	/**
	 * A Thread objects that runs this Runnable class.
	 */
	private Thread thisThread;

	/**
	 * The LPAS_DG_MS that this thread helps.
	 */
	LPAS_DG_MS lpas_dg_ms;

	/**
	 * This is how long this thread should wait after every system tick to resolve the LPAS_DG delta_star matrix.
	 */
	private double secondsAfterSystemTickToSolve = 30;
	
	/**
	 * A logger to log the results of every solution.
	 */
	private Logger logger;

	public LPAS_DG_MS_SOLVING_TICKER(LPAS_DG_MS lpas_dg) {
		this.setLpas_dg(lpas_dg);
		this.setLogger(new Logger("lpas_resolving"));
	}

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

	public LPAS_DG_MS getLpas_dg() {
		return lpas_dg_ms;
	}
	public void setLpas_dg(LPAS_DG_MS lpas_dg_ms) {
		this.lpas_dg_ms = lpas_dg_ms;
	}

	public double getSecondsAfterSystemTickToSolve() {
		return secondsAfterSystemTickToSolve;
	}
	public void setSecondsAfterSystemTickToSolve(double secondsAfterSystemTickToSolve) {
		this.secondsAfterSystemTickToSolve = secondsAfterSystemTickToSolve;
	}
	

	protected Logger getLogger() {
		return logger;
	}

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

	public void startThread() {
		thisThread = new Thread(this);
		thisThread.start();
	}

	public void stopThread() {
		this.setAlive(false);
		this.getLogger().closeStreams();
		thisThread.interrupt();
		thisThread = null;
	}
	/**
	 * This thread just wakes up every System time unit and resolves the delta star
	 * matrix.
	 * 
	 */
	public void run() {
		while (isAlive()) {
			try {
				 Thread.sleep((long) (this.getSleepTimeTillNextSystemTick() + (this.getSecondsAfterSystemTickToSolve() * 1000)));
			} catch (InterruptedException e) {
				if (!this.isAlive())
					return;
			}
			getLpas_dg().refreshDeltaStar();
			this.getLogger().log("LPAS_DG delta matrix resolved: delta: " + this.getLpas_dg().getDeltaStar().toString() );
		}
	}

	/**
	 * tickLengthInMinutes are the minutes in a tick or the time resolution for
	 * the system
	 * 
	 * @return the time in milliseconds to sleep to wake up at the right time
	 *         for the next time tick
	 */
	private long getSleepTimeTillNextSystemTick() {
		double tickLengthInMinutes = this.getLpas_dg().getMapper().getTimeResolution();
		/*
		 * Setting beginning Of the day to the time when current day began
		 * (00:00:00 or 12:00:00 AM)
		 */
		GregorianCalendar beginningOfDay = new GregorianCalendar();
		beginningOfDay.set(GregorianCalendar.HOUR_OF_DAY, 0);
		beginningOfDay.set(GregorianCalendar.MINUTE, 0);
		beginningOfDay.set(GregorianCalendar.SECOND, 0);
		beginningOfDay.set(GregorianCalendar.MILLISECOND, 0);

		/* now is a GregorianCalendar object for current moment */
		GregorianCalendar now = new GregorianCalendar();

		/* Calculating sleep time */
		// Calculating difference between now and the beginning of the day in
		// milli seconds
		long diff = now.getTimeInMillis() - beginningOfDay.getTimeInMillis();
		// Calculating how many system resolution ticks happened until now (as a
		// double value)
		double ticksInDiff = ((double) diff) / ((double) tickLengthInMinutes * 60.0 * 1000.0);
		// Calculating the sleepTime until the next tick using ticksInDiff.
		// For example suppose ticksInDiff = 3.7 ticks, and sysResolution was 10
		// minuts.
		// That means the time till the next tick is .3 tick (0.3 = ceil(3.7) -
		// 3.7.)
		// 0.3 tick is 0.3 * 10 minutes = 3 minutes = 3 minutes * 60
		// second/minute * 1000 millsecond/second = sleepTime
		double sleepTime = (Math.ceil(ticksInDiff) - ticksInDiff) * tickLengthInMinutes * 60.0 * 1000.0;

		return min(Math.round(sleepTime), this.getTimeTillMidnight(now));
		// return Math.round(sleepTime);
	}
	
	
	/**
	 * tickLengthInMinutes are the minutes in a tick or the time resolution for
	 * the system
	 * 
	 * @return the time in milliseconds to sleep to wake up at the right time
	 *         for the next time tick
	 */
	public long getSleepTimeTillNextSystemTick2( GregorianCalendar time) {
		double tickLengthInMinutes = this.getLpas_dg().getMapper().getTimeResolution();
		/*
		 * Setting beginning Of the day to the time when current day began
		 * (00:00:00 or 12:00:00 AM)
		 */
		GregorianCalendar beginningOfDay = new GregorianCalendar();
		beginningOfDay.set(GregorianCalendar.HOUR_OF_DAY, 0);
		beginningOfDay.set(GregorianCalendar.MINUTE, 0);
		beginningOfDay.set(GregorianCalendar.SECOND, 0);
		beginningOfDay.set(GregorianCalendar.MILLISECOND, 0);

		/* now is a GregorianCalendar object for current moment */
		//GregorianCalendar now = new GregorianCalendar();

		/* Calculating sleep time */
		// Calculating difference between now and the beginning of the day in
		// milli seconds
		long diff = time.getTimeInMillis() - beginningOfDay.getTimeInMillis();
		// Calculating how many system resolution ticks happened until now (as a
		// double value)
		double ticksInDiff = ((double) diff) / ((double) tickLengthInMinutes * 60.0 * 1000.0);
		// Calculating the sleepTime until the next tick using ticksInDiff.
		// For example suppose ticksInDiff = 3.7 ticks, and sysResolution was 10
		// minuts.
		// That means the time till the next tick is .3 tick (0.3 = ceil(3.7) -
		// 3.7.)
		// 0.3 tick is 0.3 * 10 minutes = 3 minutes = 3 minutes * 60
		// second/minute * 1000 millsecond/second = sleepTime
		double sleepTime = (Math.ceil(ticksInDiff) - ticksInDiff) * tickLengthInMinutes * 60.0 * 1000.0;

		return min(Math.round(sleepTime), this.getTimeTillMidnight(time));
		// return Math.round(sleepTime);
	}

	/**
	 * 
	 * @param cal
	 *            is the time you wish to know the difference from it to
	 *            midnight
	 * @return difference between cal and next midnight
	 */
	private long getTimeTillMidnight(GregorianCalendar cal) {
		GregorianCalendar nextMidnight = (GregorianCalendar) cal.clone();
		nextMidnight.set(GregorianCalendar.HOUR_OF_DAY, 0);
		nextMidnight.set(GregorianCalendar.MINUTE, 0);
		nextMidnight.set(GregorianCalendar.SECOND, 0);
		nextMidnight.set(GregorianCalendar.MILLISECOND, 0);
		nextMidnight.set(GregorianCalendar.DAY_OF_MONTH, cal.get(GregorianCalendar.DAY_OF_MONTH) + 1);

		return nextMidnight.getTimeInMillis() - cal.getTimeInMillis();
	}

	/**
	 * 
	 * @param a
	 * @param b
	 * @return minimum between a or b. If equals returns a.
	 */
	private long min(long a, long b) {
		if (a <= b)
			return a;
		else
			return b;
	}
	

}
