SprintExecutor.java

package de.slothsoft.sprintsim.execution;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;

import de.slothsoft.sprintsim.Member;
import de.slothsoft.sprintsim.Sprint;
import de.slothsoft.sprintsim.Task;
import de.slothsoft.sprintsim.config.TaskConfig;

public class SprintExecutor {

	/** The member that executed a task - a <code>Member</code>. */
	public static final String TASK_DATA_ASSIGNEE = "assignee"; //$NON-NLS-1$
	/** The member that executed a task - an <code>int</code>. */
	public static final String TASK_DATA_ASSIGNEE_INDEX = "assigneeIndex"; //$NON-NLS-1$
	/** The time needed to executed a task - a <code>double</code>. */
	public static final String TASK_DATA_NECESSARY_HOURS = "necessaryHours"; //$NON-NLS-1$

	private Member[] members;
	private TaskConfig taskConfig = new TaskConfig();

	public SprintExecutor(Member... members) {
		this.members = Objects.requireNonNull(members);
	}

	public SprintRetro execute(Sprint sprint) {
		final List<Task> tasksToDo = new ArrayList<>(Arrays.asList(sprint.getTasks()));
		// we do long tasks first
		// TODO: make this configurable?
		tasksToDo.sort(Comparator.comparing(Task::getComplexity).reversed());

		if (this.members.length == 0) return new SprintRetro(sprint, this.members, Double.NaN, Double.NaN);

		final double[] workHours = generateWorkHours(sprint.getLengthInDays());
		for (final Task task : tasksToDo) {
			final int indexOfIdleMember = IntStream.range(0, this.members.length).boxed()
					.max(Comparator.comparingDouble(index -> workHours[index.intValue()])).get().intValue();
			final Member idleMember = this.members[indexOfIdleMember];

			final double taskBaseHours = this.taskConfig.getHours(task.getComplexity());
			final double taskHours = taskBaseHours * idleMember.getWorkPerformance().getMultiplicator(task);

			task.addUserData(TASK_DATA_ASSIGNEE, idleMember);
			task.addUserData(TASK_DATA_ASSIGNEE_INDEX, Integer.valueOf(indexOfIdleMember));
			task.addUserData(TASK_DATA_NECESSARY_HOURS, Double.valueOf(taskHours));

			// remove the hours
			workHours[indexOfIdleMember] -= taskHours;
		}
		final double necessaryAdditionalHours = -DoubleStream.of(workHours).filter(d -> d < 0).sum();
		final double remainingHours = DoubleStream.of(workHours).filter(d -> d > 0).sum();
		return new SprintRetro(sprint, this.members, necessaryAdditionalHours, remainingHours);
	}

	double[] generateWorkHours(int lengthInDays) {
		final double[] result = new double[this.members.length];
		for (int i = 0; i < result.length; i++) {
			result[i] = lengthInDays * this.members[i].getWorkHoursPerDay();
		}
		return result;
	}

	public Member[] getMembers() {
		return this.members;
	}

	public SprintExecutor members(Member... newMembers) {
		setMembers(newMembers);
		return this;
	}

	public void setMembers(Member... members) {
		this.members = Objects.requireNonNull(members);
	}

	public TaskConfig getTaskConfig() {
		return this.taskConfig;
	}

	public SprintExecutor taskConfig(TaskConfig newTaskConfig) {
		setTaskConfig(newTaskConfig);
		return this;
	}

	public void setTaskConfig(TaskConfig taskConfig) {
		this.taskConfig = Objects.requireNonNull(taskConfig);
	}

}