class DefaultDeschedulerService implements DeschedulerService {

    @Override
    public List<DeschedulingResult> deschedule(Map<String, TaskRelocationPlan> plannedAheadTaskRelocationPlans) {
        List<Pair<Job, List<Task>>> allJobsAndTasks = jobOperations.getJobsAndTasks();
        Map<String, Job<?>> jobs = allJobsAndTasks.stream().map(Pair::getLeft).collect(Collectors.toMap(Job::getId, j -> j));
        Map<String, Task> tasksById = allJobsAndTasks.stream()
                .flatMap(p -> p.getRight().stream())
                .collect(Collectors.toMap(Task::getId, t -> t));

        EvacuatedAgentsAllocationTracker evacuatedAgentsAllocationTracker = new EvacuatedAgentsAllocationTracker(nodeDataResolver.resolve(), tasksById);
        EvictionQuotaTracker evictionQuotaTracker = new EvictionQuotaTracker(evictionOperations, jobs);

        TaskMigrationDescheduler taskMigrationDescheduler = new TaskMigrationDescheduler(
                plannedAheadTaskRelocationPlans, evacuatedAgentsAllocationTracker, evictionQuotaTracker, jobs, tasksById, titusRuntime
        );

        Map<String, DeschedulingResult> requestedImmediateEvictions = taskMigrationDescheduler.findAllImmediateEvictions();
        Map<String, DeschedulingResult> requestedEvictions = taskMigrationDescheduler.findRequestedJobOrTaskMigrations();
        Map<String, DeschedulingResult> allRequestedEvictions = CollectionsExt.merge(requestedImmediateEvictions, requestedEvictions);

        Map<String, DeschedulingResult> regularEvictions = new HashMap<>();
        Optional<Pair<Node, List<Task>>> bestMatch;
        while ((bestMatch = taskMigrationDescheduler.nextBestMatch()).isPresent()) {
            Node agent = bestMatch.get().getLeft();
            List<Task> tasks = bestMatch.get().getRight();
            tasks.forEach(task -> {
                if (!allRequestedEvictions.containsKey(task.getId())) {
                    TaskRelocationPlan relocationPlan = plannedAheadTaskRelocationPlans.get(task.getId());
                    if (relocationPlan == null) {
                        relocationPlan = newNotDelayedRelocationPlan(task, true);
                    }
                    regularEvictions.put(
                            task.getId(),
                            DeschedulingResult.newBuilder()
                                    .withTask(task)
                                    .withAgentInstance(agent)
                                    .withTaskRelocationPlan(relocationPlan)
                                    .build()
                    );
                }
            });
        }

        // Find eviction which could not be scheduled now.
        for (Task task : tasksById.values()) {
            if (allRequestedEvictions.containsKey(task.getId()) || regularEvictions.containsKey(task.getId())) {
                continue;
            }
            if (evacuatedAgentsAllocationTracker.isEvacuated(task)) {

                DeschedulingFailure failure = taskMigrationDescheduler.getDeschedulingFailure(task);
                TaskRelocationPlan relocationPlan = plannedAheadTaskRelocationPlans.get(task.getId());

                if (relocationPlan == null) {
                    relocationPlan = newNotDelayedRelocationPlan(task, false);
                }

                Node agent = evacuatedAgentsAllocationTracker.getRemovableAgent(task);
                regularEvictions.put(
                        task.getId(),
                        DeschedulingResult.newBuilder()
                                .withTask(task)
                                .withAgentInstance(agent)
                                .withTaskRelocationPlan(relocationPlan)
                                .withFailure(failure)
                                .build()
                );
            }
        }

        return CollectionsExt.merge(new ArrayList<>(allRequestedEvictions.values()), new ArrayList<>(regularEvictions.values()));
    }

    @Inject public  DefaultDeschedulerService(ReadOnlyJobOperations jobOperations,
                                     ReadOnlyEvictionOperations evictionOperations,
                                     NodeDataResolver nodeDataResolver,
                                     TitusRuntime titusRuntime);

}