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

import be.iminds.ilabt.jfed.experiment.Experiment;
import be.iminds.ilabt.jfed.highlevel.controller.TaskExecution;
import be.iminds.ilabt.jfed.highlevel.controller.TaskThread;
import be.iminds.ilabt.jfed.highlevel.jobs.states.JobStateFactory;
import be.iminds.ilabt.jfed.highlevel.tasks.HighLevelTaskFactory;
import be.iminds.ilabt.jfed.highlevel.tasks.OtherUserSshKeysTask;
import be.iminds.ilabt.jfed.highlevel.tasks.ShareSliceTask;
import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
import be.iminds.ilabt.jfed.util.GeniUrn;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 11/26/2015
 * Time: 18:18
 */
public class ShareSliceJob extends AbstractJob<Void> {
    private static final Logger LOG = LoggerFactory.getLogger(ShareSliceJob.class);

    private final Experiment experiment;
    private final Collection<GeniUrn> usersToShareWith;
    private final boolean registerSshKey;

    private final JobStateFactory jobStateFactory;

    public ShareSliceJob(Experiment experiment, Collection<GeniUrn> usersToShareWith, boolean registerSshKey,
                         HighLevelTaskFactory hltf, TaskThread tt, JobStateFactory jobStateFactory) {
        super(String.format("Share slice %s with %d users", experiment.getName(), usersToShareWith.size()), experiment, hltf, tt);
        this.experiment = experiment;

        this.usersToShareWith = usersToShareWith;
        this.registerSshKey = registerSshKey;
        this.jobStateFactory = jobStateFactory;

    }

    @Override
    public Void execute() throws Exception {
        setAndRunState(new ShareSliceWithUsersState());

        if (registerSshKey) {
            FetchUserKeysState fetchUserKeysState = new FetchUserKeysState();
            setAndRunState(fetchUserKeysState);

            if (fetchUserKeysState.getUserSpecs() != null) {
                setAndRunState(jobStateFactory.createUpdateSshKeysState(ShareSliceJob.this, fetchUserKeysState.getUserSpecs()));
            } else {
                LOG.warn("Not applying SSH keys to authorities as fetching keys failed");
            }
        }
        return null;
    }


    public class ShareSliceWithUsersState extends MultipleCallState<ShareSliceTask> {
        ShareSliceWithUsersState() {
            super(String.format("Share slice %s with %d users", experiment.getName(), usersToShareWith.size()),
                    ShareSliceJob.this);
        }

        @Override
        public Collection<ShareSliceTask> getTasks() {
            return usersToShareWith.stream()
                    .map(user -> hltf.getShareSliceTask(experiment.getSlice(), user))
                    .collect(Collectors.toList());
        }
    }

    public class FetchUserKeysState extends MultipleCallState<OtherUserSshKeysTask> {
        private final List<UserSpec> newUserSpecs = Collections.synchronizedList(new ArrayList<>());

        private final IntegerProperty successfulCalls = new SimpleIntegerProperty(0);
        private final IntegerProperty failedCalls = new SimpleIntegerProperty(0);

        protected FetchUserKeysState() {
            super(String.format("Fetching SSH keys of %d users", usersToShareWith.size()), ShareSliceJob.this);
        }

        @Override
        public Collection<OtherUserSshKeysTask> getTasks() {
            return usersToShareWith.stream()
                    .map(hltf::getOtherUserSshKeys)
                    .collect(Collectors.toList());
        }

        @Override
        protected void onTaskExecutionFinished(OtherUserSshKeysTask task,
                                               TaskExecution<OtherUserSshKeysTask> taskExecution,
                                               TaskExecution.TaskState state) {
            if (taskExecution.getState() == TaskExecution.TaskState.SUCCESS) {
                newUserSpecs.add(new UserSpec(task.getUserUrn().toString(), task.getKeys()));
                successfulCalls.set(successfulCalls.get() + 1);
            } else {
                failedCalls.set(failedCalls.get() + 1);
            }
        }

        public List<UserSpec> getUserSpecs() {
            return newUserSpecs;
        }
    }
}
