package be.iminds.ilabt.jfed.highlevel.jobs;

import be.iminds.ilabt.jfed.experiment.*;
import be.iminds.ilabt.jfed.experiment.tasks.ExperimentTaskStatus;
import be.iminds.ilabt.jfed.highlevel.controller.TaskThread;
import be.iminds.ilabt.jfed.highlevel.jobs.parts.ExperimentPartController;
import be.iminds.ilabt.jfed.highlevel.jobs.parts.ExperimentPartControllerManager;
import be.iminds.ilabt.jfed.highlevel.model.InternalState;
import be.iminds.ilabt.jfed.highlevel.tasks.HighLevelTaskFactory;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.StatusDetails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 12/1/2015
 * Time: 17:03
 */
public class StopExperimentJob extends AbstractJob<Void> {
    private static final Logger LOG = LoggerFactory.getLogger(StopExperimentJob.class);
    private final Collection<ExperimentPart> experimentParts;

    private final ExperimentPartControllerManager epcManager;

    public StopExperimentJob(Experiment experiment, HighLevelTaskFactory hltf, TaskThread tt, ExperimentPartControllerManager epcManager) {
        super("Delete all active resources from experiment '" + experiment.getName() + "'", experiment, hltf, tt);
        this.epcManager = epcManager;
        this.experimentParts = null;
    }

    public StopExperimentJob(Experiment experiment, Collection<ExperimentPart> experimentParts, HighLevelTaskFactory hltf, TaskThread tt, ExperimentPartControllerManager epcManager) {
        super("Delete active resources from '" + experimentParts.stream().map(ExperimentPart::getName).collect(Collectors.joining(", ")) + "'",
                experiment, hltf, tt);
        this.experimentParts = experimentParts;
        this.epcManager = epcManager;
    }


    @Override
    public Void execute() throws Exception {
        assert experiment.getSlice() != null;
        /*if (experiment.getSlice().isExpired()) {
            throw new JFedHighLevelException("Slice has expired.");
        }
        if (!experiment.getSlice().hasCredential()) {
            throw new JFedHighLevelException("No credentials for slice. Has it expired? slice expiration date = "
                    + experiment.getSlice().getExpirationDate() + " now=" + Instant.now());
        } */

        if (experimentParts == null || experimentParts.size() == experiment.getParts().size())
            experiment.setExperimentState(ExperimentState.DELETING);

        StopExperimentAtExperimentPartsSlicedState stopExperimentAtExperimentPartsState =
                new StopExperimentAtExperimentPartsSlicedState();
        setAndRunState(stopExperimentAtExperimentPartsState);


        //check if we can delete non-sfa containers

        //values are set to false if not in use, true if in use
        Map<ExperimentPartsContainer, Boolean> containersInUse = new HashMap<>();

        for (ExperimentPart experimentPart : experiment.getParts()) {
            if (experimentPart instanceof SfaExperimentPart)
                continue;

            if (experimentPart.getContainer() == null)
                continue;

            if (experimentPart.getState() != InternalState.DELETED) {
                containersInUse.put(experimentPart.getContainer(), true);
            } else if (!containersInUse.containsKey(experimentPart.getContainer())) {
                containersInUse.put(experimentPart.getContainer(), false);
            }
        }

        Collection<ExperimentPartsContainer> containersToBeRemoved =
                containersInUse.entrySet().stream()
                        .filter(entry -> !entry.getValue())
                        .map(Map.Entry::getKey)
                        .filter(ExperimentPartsContainer::shouldBeDestroyedOnTermination)
                        .collect(Collectors.toList());

        if (!containersToBeRemoved.isEmpty()) {
            setAndRunState(new StopExperimentPartsContainersState(containersToBeRemoved));
        }

        if (experimentParts == null || experimentParts.size() == experiment.getParts().size()) {
            if (stopExperimentAtExperimentPartsState.getStatus() == ExperimentTaskStatus.SUCCESS) {
                experiment.setExperimentState(ExperimentState.EMPTY);
            } else {
                LOG.warn("Error while stopping experiment: state did not return SUCCESS");
            }
        } //else: other cases are handled in ExperimentController#checkExperimentPartsStates

        //check if we need to change slice.hasActiveSlivers

        boolean hasActiveResources = false;
        for (StatusDetails.SliverStatus sliverStatus : experiment.getSlice().getStatus().getStatusBySliverUrn().values()) {
            if (sliverStatus == StatusDetails.SliverStatus.READY || sliverStatus == StatusDetails.SliverStatus.CHANGING)
                hasActiveResources = true;
        }

        for (StatusDetails.SliverStatus sliverStatus : experiment.getSlice().getStatus().getStatusByComponentUrn().values()) {
            if (sliverStatus == StatusDetails.SliverStatus.READY || sliverStatus == StatusDetails.SliverStatus.CHANGING)
                hasActiveResources = true;
        }

        if (!hasActiveResources) {
            experiment.getSlice().setHasActiveSlivers(false);
        }

        return null;
    }

    public class StopExperimentAtExperimentPartsSlicedState extends ExperimentPartsSlicedState {
        protected StopExperimentAtExperimentPartsSlicedState() {
            super("Delete all active resources from experiment '" + experiment.getName() + "'", experiment, experimentParts, epcManager);
        }

        @Override
        public ExperimentPartStateSlice createJobSlice(ExperimentPartController controller, ExperimentPart experimentPart) {
            //noinspection unchecked
            return controller.stop(StopExperimentJob.this, experimentPart);
        }
    }

    public class StopExperimentPartsContainersState extends SlicedState<StateSlice> {
        private final Collection<StateSlice> states;

        public StopExperimentPartsContainersState(Collection<ExperimentPartsContainer> containers) {
            super("Destroying " + containers.size() + " experiment container(s)");

            states = containers.stream()
                    .map(container -> epcManager.getContainerController(container)
                            .destroyContainer(StopExperimentJob.this, container))
                    .collect(Collectors.toList());
        }


        @Override
        public Collection<StateSlice> getSlices() {
            return states;
        }
    }
}
