package scheduler;

import java.io.IOException;
import java.util.logging.Logger;

import common.Direction;
import common.LoggerUtil;
import common.Motor;
import communication.MessageSender;
import communication.Port;
import communication.messages.ElevatorActionMessage;
import communication.messages.ElevatorMoveMessage;
import communication.messages.ElevatorRequestMessage;
import communication.messages.FloorArrivalMessage;
import elevator.Elevator.ElevatorAction;
import error.UnknownAddressException;
import floor.input.InputEvent;

/**
 * This class represents an elevator dispatch thread to respond to a
 * ElevatorRequest from a FloorSubsystem. The task determines which elevator is
 * to be sent to the floor making the request And sends the instruction to the
 * chosen elevator to go to the requested Floor
 */
public class ElevatorDispatchTask implements SchedulerTask {
	private static final Logger logger = LoggerUtil.getLoggerForClass(ElevatorDispatchTask.class);
	private final int destinationFloor;
	private final InputEvent inputEvent;
	private MetaElevator elevator = null;
	private Direction reroutedDirection = null;

	public ElevatorDispatchTask(ElevatorRequestMessage elevatorRequestMessage) {
		inputEvent = elevatorRequestMessage.getInputEvent();
		destinationFloor = inputEvent.getInputFloor();
	}

	public ElevatorDispatchTask(ElevatorMoveMessage elevatorMoveMessage) {
		inputEvent = null;
		destinationFloor = elevatorMoveMessage.getRequestFloor();
		elevator = Scheduler.getMetaElevatorById(elevatorMoveMessage.getElevatorId());
	}

	public ElevatorDispatchTask(ElevatorMoveMessage elevatorMoveMessage, Direction reroutedDirection) {
		inputEvent = null;
		destinationFloor = elevatorMoveMessage.getRequestFloor();
		elevator = Scheduler.getMetaElevatorById(elevatorMoveMessage.getElevatorId());
		this.reroutedDirection = reroutedDirection;
	}

	/**
	 * Tells an elevator to start moving. If it's already at the correct floor, we
	 * tell it to open its doors and tell the FloorSubsystem to turn one of its
	 * lights off. If not, we set the destination floor of the elevator and tell it
	 * to move up or down.
	 */
	@Override
	public void run(MessageSender messageSender) throws IOException, UnknownAddressException {
		if (elevator == null) {
			// get the best elevator for this request
			elevator = Scheduler.getInstance().getElevatorForRequest(destinationFloor, inputEvent.getDirectionButton());
		}

		if (destinationFloor == elevator.getCurrentFloor()) {
			// we aren't handling the case where a user enters
			// the elevator and presses the same floor they're already occupying
			if (inputEvent != null) {
				// send floorMessage telling the floor we arrived
				messageSender.sendMessage(new FloorArrivalMessage(destinationFloor,
					inputEvent.getDirectionButton(), elevator.getElevatorId()), Port.FLOOR);

				messageSender.sendMessage(new ElevatorActionMessage(elevator.getElevatorId(),
					ElevatorAction.ADD_AND_ARRIVE_AT_DESTINATION, destinationFloor, inputEvent, null), Port.ELEVATOR);

				elevator.cancelTimer();
				elevator.startTimer(Scheduler.getFloorDelayTime());
			}
		} else {
			moveFromRequestToDestination(messageSender);
		}

		if (inputEvent != null) {
			logger.info("Received request from floor " + inputEvent.getInputFloor() + " to floor "
					+ inputEvent.getCarButton() + ". Queuing elevator " + elevator.getElevatorId());
		}
	}

	private void moveFromRequestToDestination(MessageSender messageSender) throws IOException, UnknownAddressException {
		if (destinationFloor != ElevatorMoveMessage.NO_FLOOR_REQUESTS) { // input from inside elevator
			if (reroutedDirection == null) {
				elevator.addDestinationFloor(destinationFloor, inputEvent != null ? inputEvent.getDirectionButton() : null);
				// set the DESTINATION_FLOOR of the elevator
				messageSender.sendMessage(new ElevatorActionMessage(elevator.getElevatorId(),
					ElevatorAction.ADD_DESTINATION_FLOOR, destinationFloor, inputEvent, null), Port.ELEVATOR);
			} else {
				elevator.addDestinationFloor(destinationFloor, reroutedDirection);
			}
		}

		// if destinationFloor has no floor requests that means there was no input from inside elevator
		// so we need to check if the elevator still has destinations and tell it to move then and
		// only move if the elevator is currently stopped because we might be getting multiple input
		// messages from inside the elevator coming at different times so we only send one move message
		if (elevator.hasDestinations() && elevator.getMotorStatus() == Motor.STOPPED) {
			int nextDestination = elevator.getNextDestination(destinationFloor);
			if (nextDestination == elevator.getCurrentFloor()) {
				// switching directions case where there was an up request to a floor and a down request at the same floor
				elevator.removeDestinationFloor();
				nextDestination = elevator.getNextDestination();
			}
			// we can potentially miss a floor here. If we are getting multiple requests, we start moving in the direction of
			// the first request we are getting. If the time between moving floors is faster than the time it takes to receive the closest floor
			// we will miss that floor.
			boolean movingUp = nextDestination > elevator.getCurrentFloor();

			elevator.setMotorStatus(movingUp ? Motor.UP : Motor.DOWN);
			ElevatorAction upOrDown = movingUp ? ElevatorAction.MOVE_UP : ElevatorAction.MOVE_DOWN;
			messageSender.sendMessage(new ElevatorActionMessage(elevator.getElevatorId(), upOrDown, nextDestination), Port.ELEVATOR);

			elevator.startTimer(Scheduler.getFloorDelayTime());
		}
	}

	public boolean hasInputEvent() {
		return inputEvent != null;
	}
}
