package com.metservice.kanban.model;
import static com.metservice.kanban.model.WorkItem.ROOT_WORK_ITEM_ID;
import static java.lang.Math.max;
import static java.util.Collections.emptyList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//TODO This class needs unit tests.
public final class DefaultWorkItemTree implements WorkItemTree {
private Map<Integer, WorkItem> workItemsById = new HashMap<Integer, WorkItem>();
private Map<Integer, List<WorkItem>> workItemsByParentId = new HashMap<Integer, List<WorkItem>>();
@Override
public void addWorkItem(WorkItem workItem) {
workItemsById.put(workItem.getId(), workItem);
indexByParentId(workItem, workItem.getParentId());
}
private void indexByParentId(WorkItem workItem, int parentId) {
if (!workItemsByParentId.containsKey(parentId)) {
workItemsByParentId.put(parentId, new ArrayList<WorkItem>());
}
workItemsByParentId.get(parentId).add(workItem);
}
@Override
public WorkItem getWorkItem(int id) {
return workItemsById.get(id);
}
@Override
public WorkItem getParent(WorkItem workItem) {
return getWorkItem(workItem.getParentId());
}
@Override
public List<WorkItem> getChildren(int parentId) {
List<WorkItem> workItems;
if (workItemsByParentId.containsKey(parentId)) {
workItems = workItemsByParentId.get(parentId);
} else {
workItems = emptyList();
}
return new ArrayList<WorkItem>(workItems);
}
@Override
public List<WorkItem> getWorkItemList() {
List<WorkItem> workItems = new ArrayList<WorkItem>();
addDescendentsToList(ROOT_WORK_ITEM_ID, workItems);
return workItems;
}
private void addDescendentsToList(int id, List<WorkItem> list) {
List<WorkItem> children = getChildren(id);
for (WorkItem child : children) {
list.add(child);
addDescendentsToList(child.getId(), list);
}
}
@Override
public List<WorkItem> getWorkItemsOfType(WorkItemType type, String workStream) {
Collection<WorkItem> workItems = new ArrayList<WorkItem>();
for (WorkItem workItem : getWorkItemList()) {
if (workItem.getType().equals(type) && workItem.isInWorkStream(workStream)) {
workItems.add(workItem);
}
}
return new ArrayList<WorkItem>(workItems);
}
@Override
public WorkItem getTopLevelAncestor(WorkItem workItem) {
int parentId = workItem.getParentId();
if (parentId == ROOT_WORK_ITEM_ID) {
return workItem;
}
return getTopLevelAncestor(getWorkItem(parentId));
}
@Override
public int getNewId() {
int largestIdInUse = ROOT_WORK_ITEM_ID;
for (WorkItem workItem : getWorkItemList()) {
largestIdInUse = max(largestIdInUse, workItem.getId());
}
return largestIdInUse + 1;
}
@Override
public List<WorkItem> getChildrenWithType(int parentId, WorkItemType childType, String workStream) {
List<WorkItem> workItems = new ArrayList<WorkItem>();
for (WorkItem workItem : getChildren(parentId)) {
if (workItem.getType().equals(childType) && workItem.isInWorkStream(workStream)) {
workItems.add(workItem);
}
}
return new ArrayList<WorkItem>(workItems);
}
@Override
public void delete(int id) {
if (!workItemsById.containsKey(id)) {
throw new IllegalArgumentException("unknown work item id: " + id);
}
if (!getChildren(id).isEmpty()) {
throw new IllegalStateException("cannot delete work item with children");
}
WorkItem item = getWorkItem(id);
workItemsById.remove(id);
workItemsByParentId.get(item.getParentId()).remove(item);
}
@Override
public void reparent(int id, int newParentId) {
WorkItem oldWorkItem = workItemsById.remove(id);
List<WorkItem> siblings = workItemsByParentId.get(oldWorkItem.getParentId());
siblings.remove(oldWorkItem);
addWorkItem(oldWorkItem.withNewParent(newParentId));
}
@Override
public Collection<WorkItem> getParentAlternatives(WorkItem workItem) {
return workItem.getParentId() == ROOT_WORK_ITEM_ID
? Collections.<WorkItem> singleton(getRoot())
: getChildren(getParent(workItem).getParentId());
}
public WorkItem getRoot() {
WorkItem root = new WorkItem(0, -1, null);
root.setName("Top level");
return root;
}
public void addWorkItems(WorkItem... workItems) {
for (WorkItem workItem : workItems) {
addWorkItem(workItem);
}
}
private List<WorkItem> filteredList(WorkItem workItem, List<WorkItem> workItemList) {
List<WorkItem> newList = new ArrayList<WorkItem>();
for (WorkItem item : workItemList) {
if (item.getCurrentPhase() == workItem.getCurrentPhase() && item != workItem) {
newList.add(item);
}
}
return newList;
}
private void verifyReorder(WorkItem workItem, List<WorkItem> workItemList) {
List<WorkItem> currentList = filteredList(workItem, workItemsByParentId.get(workItem.getParentId()));
List<WorkItem> newList = filteredList(workItem, workItemList);
if (!Arrays.equals(currentList.toArray(), newList.toArray())) {
throw new IllegalStateException("Tree was changed by someone else.\nCurrent\n" + currentList + "\nNew\n"
+ newList);
}
}
@Override
public void reorder(WorkItem workItem, List<WorkItem> workItemList) {
verifyReorder(workItem, workItemList);
int pos = workItemList.indexOf(workItem);
move(workItem, pos == 0 ? null : workItemList.get(pos - 1), true);
}
@Override
public void move(WorkItem workItem, WorkItem target, boolean after) {
List<WorkItem> siblings = workItemsByParentId.get(workItem.getParentId());
siblings.remove(workItem);
int pos = siblings.indexOf(target) + (after ? 1 : 0);
siblings.add(pos < 0 ? siblings.size() : pos, workItem);
}
@Override
public Map<Integer, WorkItem> getWorkItemsById() {
return workItemsById;
}
}