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

import be.iminds.ilabt.jfed.experiment.Experiment;
import be.iminds.ilabt.jfed.experiment.ExperimentPart;
import be.iminds.ilabt.jfed.experiment.tasks.ExperimentTaskStatus;
import be.iminds.ilabt.jfed.highlevel.controller.JFedHighLevelException;
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.tasks.HighLevelTaskFactory;
import be.iminds.ilabt.jfed.highlevel.tasks.RenewSliceTask;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 12/2/2015
 * Time: 15:54
 */
public class RenewJob extends AbstractJob<ExperimentTaskStatus> {

    private final Instant newExpirationDate;
    private final Collection<ExperimentPart> experimentParts;

    private final ExperimentPartControllerManager epcManager;
    private List<ExperimentPart> failedParts;
    private boolean renewSliceFailed;

    public RenewJob(Experiment experiment, Instant newExpirationDate,
                    HighLevelTaskFactory hltf, TaskThread tt, ExperimentPartControllerManager epcManager) {
        super("Renew experiment until " + DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.ofInstant(newExpirationDate, ZoneId.systemDefault())),
                experiment, hltf, tt);
        this.newExpirationDate = newExpirationDate;
        this.epcManager = epcManager;
        this.experimentParts = null;
    }

    public RenewJob(Experiment experiment, Instant newExpirationDate, Collection<ExperimentPart> experimentParts,
                    HighLevelTaskFactory hltf, TaskThread tt, ExperimentPartControllerManager epcManager) {
        super("Renew experiment until " + DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.ofInstant(newExpirationDate, ZoneId.systemDefault())),
                experiment, hltf, tt);
        this.newExpirationDate = newExpirationDate;
        this.experimentParts = experimentParts;
        this.epcManager = epcManager;
    }

    @Override
    public ExperimentTaskStatus execute() throws Exception {

        renewSliceFailed = false;
        assert experiment.getSlice() != null;
        if (experiment.getSlice().getExpirationDate() == null ||
                newExpirationDate.isAfter(experiment.getSlice().getExpirationDate())) {
            final RenewSliceState renewSliceState = new RenewSliceState();
            setAndRunState(renewSliceState);

            //don't throw an exception here: just try to renew the slivers anyway. We will throw the exception at
            //the end of the method
            renewSliceFailed =  (renewSliceState.getStatus() != ExperimentTaskStatus.SUCCESS);
        }

        RenewExperimentPartsState renewPartsState = new RenewExperimentPartsState();
        setAndRunState(renewPartsState);

        failedParts = renewPartsState.getSlices().stream()
                .filter(js -> js.getResult() != ExperimentTaskStatus.SUCCESS)
                .map(ExperimentPartStateSlice::getExperimentPart)
                .collect(Collectors.toList());

        if (!failedParts.isEmpty()) {
            updateMessage("Renewing failed at " + failedParts.stream().map(ExperimentPart::getName).collect(Collectors.joining(", ")));
        }

        if(renewSliceFailed)
            throw new JFedHighLevelException("Could not renew slice!");

        return renewPartsState.getStatus();
    }

    private class RenewSliceState extends SingleCallState<RenewSliceTask> {

        protected RenewSliceState() {
            super("Renewing experiment container", hltf.renewSlice(experiment.getSlice(), newExpirationDate));
        }
    }

    private class RenewExperimentPartsState extends ExperimentPartsSlicedState<ExperimentPartStateSlice> {
        protected RenewExperimentPartsState() {
            super("Renewing resources at %d authorities", experiment, experimentParts, epcManager);
        }

        @Override
        public <EP extends ExperimentPart> ExperimentPartStateSlice createJobSlice(ExperimentPartController<EP> controller, EP experimentPart) {
            return controller.renew(RenewJob.this, experimentPart, newExpirationDate);
        }
    }

    public boolean isRenewSliceFailed() {
        return renewSliceFailed;
    }

    public List<ExperimentPart> getFailedParts() {
        return Collections.unmodifiableList(failedParts);
    }
}
