package com.metservice.kanban.model;
import static java.lang.Math.round;
import static java.lang.Math.sqrt;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TreeMap;
import org.joda.time.LocalDate;
public class EstimatesProject {
private final List<WorkItem> plannedFeatures = new ArrayList<WorkItem>();
private final List<WorkItem> completedFeatures = new ArrayList<WorkItem>();
private int budget = 0;
private int estimatedCostPerPoint = 0;
private Map<LocalDate, Integer> costDailyMap = new TreeMap<LocalDate, Integer>();
private String projectName;
private KanbanProject kanbanProject;
public WorkItem getFeature(int id) {
return plannedFeatures.get(getIndexOfFeature(id));
}
private int getIndexOfFeature(int id) {
for (int index = 0; index < plannedFeatures.size(); index++) {
if (plannedFeatures.get(index).getId() == id) {
return index;
}
}
throw new NoSuchElementException("feature id = " + id);
}
public List<EstimatesBudgetEntry> getBudgetEntries() {
List<EstimatesBudgetEntry> entries = new ArrayList<EstimatesBudgetEntry>();
for (int i = 0; i < plannedFeatures.size(); i++) {
WorkItem previous = null;
WorkItem next = null;
if (i > 0) {
previous = plannedFeatures.get(i - 1);
}
if (i < plannedFeatures.size() - 1) {
next = plannedFeatures.get(i + 1);
}
WorkItem current = plannedFeatures.get(i);
EstimatesBudgetEntry entry = new EstimatesBudgetEntry(current, previous, next);
entries.add(entry);
}
calculateCumulativeCostAverageGuess(entries);
calculateCumulativeCostWorstCase(entries);
identifyBudgetOverruns(entries);
return entries;
}
private void calculateCumulativeCostAverageGuess(List<EstimatesBudgetEntry> entries) {
int cumulativePoints = 0;
int costSoFar = getCostSoFar();
for (EstimatesBudgetEntry entry : entries) {
cumulativePoints += entry.getFeature().getAverageCaseEstimate();
entry.setAverageCaseCumulativeCost(costSoFar + cumulativePoints * estimatedCostPerPoint);
}
}
private void calculateCumulativeCostWorstCase(List<EstimatesBudgetEntry> entries) {
int cumulativePointVariance = 0;
for (EstimatesBudgetEntry entry : entries) {
cumulativePointVariance += entry.getFeature().getVariance();
int buffer = (int) round(sqrt(cumulativePointVariance) * estimatedCostPerPoint);
entry.setWorstCaseCumulativeCost(entry.getAverageCaseCumulativeCost() + buffer);
}
}
private void identifyBudgetOverruns(List<EstimatesBudgetEntry> entries) {
for (EstimatesBudgetEntry entry : entries) {
if (entry.getAverageCaseCumulativeCost() > budget) {
entry.setOverBudgetInAverageCase(true);
}
if (entry.getWorstCaseCumulativeCost() > budget) {
entry.setOverBudgetInWorstCase(true);
}
}
}
public void setBudget(int budget) {
this.budget = budget;
}
public int getBudget() {
return budget;
}
public int getCostSoFar() {
int result = 0;
for (LocalDate day : costDailyMap.keySet()) {
result += costDailyMap.get(day);
}
return result;
}
public void setEstimatedCostPerPoint(int estimatedCostPerPoint) {
this.estimatedCostPerPoint = estimatedCostPerPoint;
}
public int getEstimatedCostPerPoint() {
return estimatedCostPerPoint;
}
public int getCostPerPointSoFar() {
if (getCompletedPoints() == 0) {
return 0;
}
return round((float) getCostSoFar() / (float) getCompletedPoints());
}
private int getCompletedPoints() {
int sum = 0;
for (WorkItem feature : completedFeatures) {
sum += feature.getAverageCaseEstimate();
}
return sum;
}
public List<WorkItem> getCompletedFeatures() {
return completedFeatures;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String name) {
this.projectName = name;
}
public void setKanbanProject(KanbanProject kanbanProject) {
this.kanbanProject = kanbanProject;
if (kanbanProject == null) {
completedFeatures.clear();
plannedFeatures.clear();
} else {
List<WorkItem> workItemList = kanbanProject.getWorkItemTree().getWorkItemList();
for (WorkItem wi : workItemList) {
if (wi.isTopLevel()) {
if (wi.isCompleted()) {
completedFeatures.add(wi);
} else {
plannedFeatures.add(wi);
}
}
}
}
}
public KanbanProject getKanbanProject() {
return kanbanProject;
}
public void setDayCosts(Map<LocalDate, Integer> costDailyMap) {
this.costDailyMap = costDailyMap;
}
public Map<LocalDate, Integer> getDayCosts() {
return costDailyMap;
}
}