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.SfaExperimentPart;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.lowlevel.api.AbstractGeniAggregateManager;
import be.iminds.ilabt.jfed.lowlevel.stitching.StitchingCallData;
import be.iminds.ilabt.jfed.lowlevel.stitching.StitchingDirector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.*;

/**
 * User: twalcari
 * Date: 1/27/2016
 * Time: 10:57
 */
class BasicAllocationDirector implements AllocationDirector {
    private static final Logger LOG = LoggerFactory.getLogger(BasicAllocationDirector.class);
    private final Experiment experiment;
    private boolean gaveUp = false;

    private final HashMap<SfaExperimentPart, StitchingCallData.State> experimentPartStates = new HashMap<>();

    BasicAllocationDirector(Experiment experiment) {
        this.experiment = experiment;

    }

    @Override
    public void reportGiveUp() {
        gaveUp = true;
    }

    @Override
    public boolean isGaveUp() {
        return gaveUp;
    }

    @Override
    public boolean areAnyActionsLeft() {
        if (gaveUp)
            return false;

        updateExperimentParts();

        for (StitchingCallData.State state : experimentPartStates.values()) {
            if (state != StitchingCallData.State.ALLOCATED)
                return true;
        }

        return false;
    }

    @Override
    public synchronized List<StitchingDirector.StitchAction> getStitchActions() {
        if (gaveUp)
            return Collections.emptyList();

        updateExperimentParts();

        List<StitchingDirector.StitchAction> actions = new ArrayList<>();

        for (Map.Entry<SfaExperimentPart, StitchingCallData.State> entry : experimentPartStates.entrySet()) {

            if (entry.getValue() == StitchingCallData.State.NONE) {
                actions.add(new StitchingDirector.StitchAction(StitchingDirector.StichActionType.ALLOCATE,
                        entry.getKey().getConnectSfaAuthority(), null, null, null));
                entry.setValue(StitchingCallData.State.ALLOCATING);
            }

            if (entry.getValue() == StitchingCallData.State.MUST_DELETE) {
                actions.add(new StitchingDirector.StitchAction(StitchingDirector.StichActionType.DELETE,
                        entry.getKey().getConnectSfaAuthority(), null, null, null));
            }
        }

        LOG.debug("Returning {} actions: {}", actions.size(), actions);
        return actions;
    }

    @Override
    public StitchingDirector.AllocateVerdict processAllocateResult(StitchingDirector.StitchAction stitchAction,
                                                                   AbstractGeniAggregateManager.AggregateManagerReply reply) {
        if (gaveUp)
            return StitchingDirector.AllocateVerdict.GIVEUP;


        SfaExperimentPart experimentPart = experiment.getPart(stitchAction.getServer());

        if (reply.getGeniResponseCode().isSuccess()) {
            experimentPartStates.put(experimentPart, StitchingCallData.State.ALLOCATED);
            return StitchingDirector.AllocateVerdict.OK;
        } else {
            experimentPartStates.put(experimentPart, StitchingCallData.State.NONE);
//           was: if (StitchingDirector.isUnrecoverableError(reply)) {
            //  but there are not really any recoverable errors in the non stitching scenario!
            //  So we do this instead:
            //if (!reply.getGeniResponseCode().isBusy()) {
            //    gaveUp = true;
            //    return StitchingDirector.AllocateVerdict.GIVEUP;
            //} else
            //    return StitchingDirector.AllocateVerdict.DIRECTOR_ACTION;
            //however, that is not usefull, since AbstractApi handles busy retries at a lower level already
            // so in conclusion: any error here is fatal!
            gaveUp = true;
            return StitchingDirector.AllocateVerdict.GIVEUP;
        }
    }

    @Override
    public void processListResourcesResult(Server auth, @Nullable String advertisementRspec) {
        LOG.warn("Received list resources, but this is unexpected!");
    }

    @Override
    public void processDeleteResult(Server auth) {
        SfaExperimentPart experimentPart = experiment.getPart(auth);
        experimentPartStates.put(experimentPart, StitchingCallData.State.NONE);
    }

    @Override
    public StitchingDirector.AllocateVerdict reportReadyForRetry(Server auth) {
        SfaExperimentPart experimentPart = experiment.getPart(auth);
        experimentPartStates.put(experimentPart, StitchingCallData.State.NONE);

        return null;
    }

    private void updateExperimentParts(){
        for (ExperimentPart experimentPart : experiment.getParts()) {
            //noinspection SuspiciousMethodCalls: SfaExperimentPart is subclass of ExperimentPart
            if(experimentPartStates.containsKey(experimentPart))
                continue;

            if (!(experimentPart instanceof SfaExperimentPart)) {
                //skipping
                continue;

            }
            SfaExperimentPart sfaExperimentPart = (SfaExperimentPart) experimentPart;

            if (sfaExperimentPart.getConnectSfaAuthority().isEdgeVlan())
                continue;

            experimentPartStates.put(sfaExperimentPart, StitchingCallData.State.NONE);
        }
    }
}
