package scheduler;

import java.util.ArrayList;
import java.util.List;

import common.Direction;
import common.Util;

public class SchedulerUtil {

	/**
	 * Returns the closest elevator to the specified floor
	 *
	 * @param elevators
	 * @param floor
	 * @return
	 */
	public static MetaElevator getClosestElevatorToFloor(List<MetaElevator> elevators, int floor) {
		// get the closest elevator
		MetaElevator closestElevator = elevators.get(0);
		int minFloorDif = Math.abs(closestElevator.getCurrentFloor() - floor);

		for (MetaElevator e : elevators) {
			int tempDif = Math.abs(e.getCurrentFloor() - floor);
			if (tempDif < minFloorDif) {
				minFloorDif = tempDif;
				closestElevator = e;
			}
		}

		return closestElevator;
	}

	/**
	 * Returns the elevator with the least distance to travel before getting to the
	 * floor
	 *
	 * @param elevators
	 * @param floor
	 * @return
	 */
	public static MetaElevator getElevatorWithLeastTravelDistanceToFloor(List<MetaElevator> elevators, int floor, Direction direction) {
		// get the elevator with the closest final destination to the floor
		MetaElevator selectedElevator = elevators.get(0);
		int minDistanceToTravel = getElevatorDistanceToTravelToFloor(elevators.get(0), floor, direction);

		for (MetaElevator e : elevators) {
			int tempDist = getElevatorDistanceToTravelToFloor(e, floor, direction);
			if (tempDist < minDistanceToTravel) {
				minDistanceToTravel = tempDist;
				selectedElevator = e;
			}
		}

		return selectedElevator;
	}

	/**
	 * Returns a list of elevators that will be passing by the specified floor
	 *
	 * @param elevators list of meta elevators that are passing by the specified floor
	 * @param floor
	 * @param direction
	 * @return
	 */
	public static List<MetaElevator> getElevatorsPassingByFloor(List<MetaElevator> elevators, int floor, Direction direction) {
		List<MetaElevator> passingElevators = new ArrayList<>();

		elevators.forEach(e -> {
			if (isElevatorPassingByFloor(e, floor, direction)) {
				passingElevators.add(e);
			}
		});

		return passingElevators;
	}

	/**
	 * Returns true if an elevator will be passing by a given a floor
	 *
	 * @param elevator
	 * @param floor
	 * @param direction
	 * @return
	 */
	public static boolean isElevatorPassingByFloor(MetaElevator elevator, int floor, Direction direction) {
		// Check if elevator's direction is same as the passenger's request
		Direction elevatorDirection = Direction.mapDirection(elevator.getMotorStatus()) ;
		if (elevatorDirection != direction || elevatorDirection == null) { // doesnt make sense to have null in this case, MAYBE need to catch it
			return false;
		}
		List<Integer> destinations = elevator.getDestinations(direction);

		if (destinations == null || destinations.isEmpty()) {
			return false;
		} else if (destinations.size() == 1) {
			return Util.isBetween(elevator.getCurrentFloor(), destinations.get(0), floor);
		}

		for (int i = 0; i < destinations.size() - 1; i++) {
			if (Util.isBetween(destinations.get(i), destinations.get(i + 1), floor)) {
				return true;
			}
		}

		return true;
	}

	/**
	 * Get total distance an elevator has to travel with its current destinations to
	 * a floor
	 *
	 * @param elevator
	 * @param floor
	 * @param inputDirection
	 * @return distance to travel to floor
	 */
	public static int getElevatorDistanceToTravelToFloor(MetaElevator elevator, int floor, Direction inputDirection) {
		Direction direction = Direction.mapDirection(elevator.getMotorStatus());
		int currentFloor = elevator.getCurrentFloor();

		// // first get total distance elevator will travel with its destinations in each direction
		int sum = 0;
		// depending on which direction the elevator is currently moving, we need to add the initial distance,
		// distance between servicing the two directions, and the final distance
		// TODO: will clean this up later
		if (direction == Direction.UP) {
			if (!elevator.hasDestinations(Direction.UP)) {
				// servicing a DOWN request but have to go up to get there
				sum += Math.abs(elevator.getNextDestination(Direction.DOWN) - currentFloor);
				sum += Math.abs(elevator.getNextDestination(Direction.DOWN) - elevator.getLastDestination(Direction.DOWN));
				sum += Math.abs(elevator.getLastDestination(Direction.DOWN) - floor);
			} else {
				sum += Math.abs(currentFloor - elevator.getNextDestination(Direction.UP));
				// distance to start servicing down requests after finishing up requests
				if (inputDirection != Direction.UP && elevator.hasDestinations(Direction.DOWN)) {
					// different direction and has down requests
					sum += Math.abs(elevator.getLastDestination(Direction.UP) - elevator.getNextDestination(Direction.UP));
					sum += Math.abs(elevator.getLastDestination(Direction.UP) - elevator.getNextDestination(Direction.DOWN));
					sum += Math.abs(elevator.getLastDestination(Direction.DOWN) - elevator.getNextDestination(Direction.DOWN));
					sum += Math.abs(elevator.getLastDestination(Direction.DOWN) - floor);
				} else {
					if (elevator.getLastDestination(Direction.UP) > floor) {
						sum += Math.abs(elevator.getLastDestination(Direction.UP) - elevator.getNextDestination(Direction.UP));
						sum += Math.abs(elevator.getLastDestination(Direction.UP) - floor);
					} else {
						sum += Math.abs(elevator.getNextDestination(Direction.UP) - floor);
					}
				}
			}
		} else {
			if (!elevator.hasDestinations(Direction.DOWN)) {
				// servicing an UP request but have to go up to get there
				sum += Math.abs(elevator.getNextDestination(Direction.UP) - currentFloor);
				sum += Math.abs(elevator.getNextDestination(Direction.UP) - elevator.getLastDestination(Direction.UP));
				sum += Math.abs(elevator.getLastDestination(Direction.UP) - floor);
			} else {
				sum += Math.abs(currentFloor - elevator.getNextDestination(Direction.DOWN));
				// distance to start servicing up requests after finishing down requests
				if (inputDirection != Direction.DOWN && elevator.hasDestinations(Direction.UP)) {
					sum += Math.abs(elevator.getLastDestination(Direction.DOWN) -
						elevator.getNextDestination(Direction.DOWN));
					sum += Math.abs(elevator.getNextDestination(Direction.UP) - elevator.getLastDestination(Direction.DOWN));
					sum += Math.abs(elevator.getNextDestination(Direction.UP) - elevator.getLastDestination(Direction.UP));
					sum += Math.abs(elevator.getLastDestination(Direction.UP) - floor);
				} else {
					if (elevator.getLastDestination(Direction.DOWN) < floor) {
						sum += Math.abs(elevator.getLastDestination(Direction.DOWN) -
								elevator.getNextDestination(Direction.DOWN));
						sum += Math.abs(elevator.getLastDestination(Direction.DOWN) - floor);
					} else {
						sum += Math.abs(elevator.getNextDestination(Direction.DOWN) - floor);
					}
				}
			}
		}
		return sum;
	}
}
