/*
 * Queue - A Queueing system that can be used to handle labs in higher education
 * Copyright (C) 2016-2021  Delft University of Technology
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package nl.tudelft.queue.model.labs;

import static nl.tudelft.librador.SpringContext.getBean;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.validation.constraints.NotNull;

import lombok.*;
import lombok.experimental.SuperBuilder;
import nl.tudelft.labracore.api.dto.RoomDetailsDTO;
import nl.tudelft.labracore.api.dto.SessionDetailsDTO;
import nl.tudelft.librador.dto.view.View;
import nl.tudelft.queue.cache.SessionCacheManager;
import nl.tudelft.queue.dto.create.QueueSessionCreateDTO;
import nl.tudelft.queue.dto.create.labs.CapacitySessionCreateDTO;
import nl.tudelft.queue.dto.patch.QueueSessionPatchDTO;
import nl.tudelft.queue.dto.patch.labs.CapacitySessionPatchDTO;
import nl.tudelft.queue.dto.view.labs.CapacitySessionViewDTO;
import nl.tudelft.queue.model.QueueSession;
import nl.tudelft.queue.model.Request;
import nl.tudelft.queue.model.SelectionRequest;
import nl.tudelft.queue.model.embeddables.CapacitySessionConfig;
import nl.tudelft.queue.model.enums.QueueSessionType;
import nl.tudelft.queue.service.LabService;

@Data
@Entity
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@DiscriminatorValue("CAPACITY")
@EqualsAndHashCode(callSuper = true)
public class CapacitySession extends QueueSession<SelectionRequest> {

	/**
	 * The embedded configuration of exam lab picking within this lab.
	 */
	@NotNull
	@Embedded
	@Builder.Default
	private CapacitySessionConfig capacitySessionConfig = new CapacitySessionConfig();

	@Override
	public boolean allowsRequest(Request<?> request) {
		var session = getBean(SessionCacheManager.class).getRequired(getSession(),
				id -> getBean(LabService.class).deleteSession(this));

		boolean allowed = request instanceof SelectionRequest && super.allowsRequest(request);
		if (capacitySessionConfig.getProcedure().isFcfs()) {
			allowed = allowed && request.getRoom() != null && session.getRooms().stream()
					.anyMatch(room -> Objects.equals(room.getId(), request.getRoom()));
		} else {
			allowed = allowed && request.getRoom() == null;
		}
		return allowed;
	}

	@Override
	public Optional<SelectionRequest> getOpenRequestForPerson(Long personId) {
		return getRequests().stream()
				.filter(r -> Objects.equals(r.getRequester(), personId))
				.filter(SelectionRequest::isRevokable)
				.findFirst();
	}

	/**
	 * Finds the last finished request for the person with the given id.
	 *
	 * @param  personId The id of the person to look for finished requests for.
	 * @return          A finished request requested by the person with the given id.
	 */
	public Optional<SelectionRequest> getProcessedSelectionRequest(Long personId) {
		return getRequests().stream()
				.filter(r -> personId.equals(r.getRequester())
						&& r.getEventInfo().getStatus().hasSelectedState())
				.findFirst();
	}

	/**
	 * Calculates the number of open spaces in the given room. This should be based on the capacity of the
	 * room and the number of requests currently selected in the room.
	 *
	 * @param  room The room for which the currently open spots should be calculated.
	 * @return      The number of currently open spots.
	 */
	public int openSpacesInRoom(RoomDetailsDTO room) {
		int nSelected = getSelectedRequestsInRoom(room.getId()).size();
		if (room.getCapacity() == null || nSelected >= room.getCapacity()) {
			return 0;
		}
		return room.getCapacity() - nSelected;
	}

	/**
	 * Gets the list of requests that are currently still pending for the room with the given id.
	 *
	 * @param  roomId The id of the room to find requests for.
	 * @return        The list of pending requests for the given room.
	 */
	public List<SelectionRequest> getPendingRequestsInRoom(Long roomId) {
		return getRequests().stream()
				.filter(r -> r.getEventInfo().getStatus().isPending() &&
						Objects.equals(r.getRoom(), roomId))
				.collect(Collectors.toList());
	}

	/**
	 * Gets the list of requests that are currently selected in the room with the given id.
	 *
	 * @param  roomId The id of the room to find requests for.
	 * @return        The list of selected requests for the given room.
	 */
	public List<SelectionRequest> getSelectedRequestsInRoom(Long roomId) {
		return getRequests().stream()
				.filter(r -> r.getEventInfo().getStatus().isSelected() &&
						Objects.equals(r.getRoom(), roomId))
				.collect(Collectors.toList());
	}

	/**
	 * Gets the list of currently selected requests.
	 *
	 * @return The list of selected requests.
	 */
	public List<SelectionRequest> getSelectedRequests() {
		return getRequests().stream()
				.filter(r -> r.getEventInfo().getStatus().isSelected())
				.collect(Collectors.toList());
	}

	/**
	 * Gets the list of currently selected or not selected requests.
	 *
	 * @return The list of requests with a selected or not selected status.
	 */
	public List<SelectionRequest> getProcessedRequests() {
		return getRequests().stream()
				.filter(r -> r.getEventInfo().getStatus().hasSelectedState())
				.collect(Collectors.toList());
	}

	/**
	 * Gets the type of this lab.
	 *
	 * @return The type of this lab.
	 */
	@Override
	protected QueueSessionType getLabType() {
		return QueueSessionType.CAPACITY;
	}

	@Override
	public QueueSessionCreateDTO<?> copyLabCreateDTO(SessionDetailsDTO session) {
		return new CapacitySessionCreateDTO(session, this);
	}

	@Override
	public QueueSessionPatchDTO<?> newPatchDTO() {
		return new CapacitySessionPatchDTO();
	}

	@Override
	public CapacitySessionViewDTO toViewDTO() {
		return View.convert(this, CapacitySessionViewDTO.class);
	}
}
