package scheduler;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import common.LoggerUtil;

/**
 * A static utility class to measure the time it takes to execute scheduler
 * tasks
 */
public class SchedulerTimer {
	private static final Logger logger = LoggerUtil.getLoggerForClass(Scheduler.class);
	private static final Map<ElevatorDispatchTask, TaskTime> dispatchRequestTaskMap = new HashMap<>();
	private static final Map<ElevatorDispatchTask, TaskTime> dispatchMoveTaskMap = new HashMap<>();
	private static final Map<ElevatorMovedTask, TaskTime> movedTaskMap = new HashMap<>();

	private SchedulerTimer() {
		throw new UnsupportedOperationException("Not Implemented");
	}

	/**
	 * Add a scheduler task to be tracked
	 *
	 * @param schedulerTask the scheduler task
	 */
	public static void addSchedulerTask(SchedulerTask schedulerTask) {
		if (schedulerTask instanceof ElevatorDispatchTask) {
			ElevatorDispatchTask dispatchTask = (ElevatorDispatchTask) schedulerTask;
			Map<ElevatorDispatchTask, TaskTime> matchingMap = dispatchTask.hasInputEvent() ? dispatchRequestTaskMap
					: dispatchMoveTaskMap;
			matchingMap.put(dispatchTask, new TaskTime(System.nanoTime()));
		} else if (schedulerTask instanceof ElevatorMovedTask) {
			movedTaskMap.put((ElevatorMovedTask) schedulerTask, new TaskTime(System.nanoTime()));
		}
	}

	/**
	 * Finish tracking a scheduler task
	 *
	 * @param task the task that was finished
	 */
	public static void setTaskComplete(SchedulerTask task) {
		long stopTimeNs = System.nanoTime();
		TaskTime taskTime = null;

		if (dispatchRequestTaskMap.containsKey(task)) {
			taskTime = dispatchRequestTaskMap.get(task);
		} else if (dispatchMoveTaskMap.containsKey(task)) {
			taskTime = dispatchMoveTaskMap.get(task);
		} else if (movedTaskMap.containsKey(task)) {
			taskTime = movedTaskMap.get(task);
		}

		if (taskTime != null) {
			taskTime.setStopTime(stopTimeNs);
		}
	}

	public static void outputSchedulerPerformance() {
		double requestDispatchTimeAverage = calculateTaskTimeAverage(dispatchRequestTaskMap);
		double moveDispatchTimeAverage = calculateTaskTimeAverage(dispatchMoveTaskMap);
		double movedTaskTimeAverage = calculateTaskTimeAverage(movedTaskMap);

		double requestDispatchTimeVariance = calculateTaskTimeVariance(dispatchRequestTaskMap,
				requestDispatchTimeAverage);
		double moveDispatchTimeVariance = calculateTaskTimeVariance(dispatchMoveTaskMap, moveDispatchTimeAverage);
		double movedTaskTimeVariance = calculateTaskTimeVariance(movedTaskMap, movedTaskTimeAverage);

		logger.info("System performance analysis complete!");

		String prefixString = "The average and variance times of the scheduler ";

		logger.info(prefixString + "servicing a request for an elevator are: " + requestDispatchTimeAverage + "ms and "
				+ requestDispatchTimeVariance + "ms");

		logger.info(prefixString + "servicing a move request from inside an elevator are: " + moveDispatchTimeAverage
				+ "ms and " + moveDispatchTimeVariance + "ms");

		logger.info(prefixString + "processing that an elevator has moved between floors are: " + movedTaskTimeAverage
				+ "ms and " + movedTaskTimeVariance + "ms");
	}

	private static double calculateTaskTimeAverage(Map<? extends SchedulerTask, TaskTime> map) {
		if (map.isEmpty()) {
			return 0;
		}

		double sum = map.values().stream().map(TaskTime::getElapsedTime).mapToDouble(l -> l / 1000000).sum();

		return sum / map.size();
	}

	private static double calculateTaskTimeVariance(Map<? extends SchedulerTask, TaskTime> map, double average) {
		if (map.isEmpty()) {
			return 0;
		}

		List<Double> taskTimes = map.values().stream().map(TaskTime::getElapsedTime).map(l -> l / 1000000)
				.collect(Collectors.toList());

		double temp = 0;
		for (double a : taskTimes) {
			temp += ((a - average) * (a - average));
		}
		return temp / map.size();
	}
}
