package scrum.client.project;
import ilarkesto.core.scope.Scope;
import ilarkesto.gwt.client.Date;
import ilarkesto.gwt.client.Gwt;
import ilarkesto.gwt.client.HyperlinkWidget;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import scrum.client.ScrumGwt;
import scrum.client.admin.Auth;
import scrum.client.admin.User;
import scrum.client.collaboration.ForumSupport;
import scrum.client.common.ReferenceSupport;
import scrum.client.common.ShowEntityAction;
import scrum.client.estimation.RequirementEstimationVote;
import scrum.client.impediments.Impediment;
import scrum.client.issues.Issue;
import scrum.client.sprint.Sprint;
import scrum.client.sprint.Task;
import com.google.gwt.user.client.ui.Widget;
public class Requirement extends GRequirement implements ReferenceSupport, ForumSupport {
public static final String REFERENCE_PREFIX = "sto";
public static String[] WORK_ESTIMATION_VALUES = new String[] { "", "0.5", "1", "2", "3", "5", "8", "13", "20",
"40", "100" };
private transient EstimationBar estimationBar;
private transient Comparator<Task> tasksOrderComparator;
public Requirement(Project project) {
setProject(project);
setDirty(true);
}
public Requirement(Issue issue) {
setProject(issue.getProject());
setLabel(issue.getLabel());
setDescription(issue.getDescription());
}
public Requirement(Map data) {
super(data);
}
public boolean isDecidable() {
if (!isTasksClosed()) return false;
if (getRejectDate() != null) return false;
return true;
}
public boolean isRejected() {
if (isClosed()) return false;
if (!isTasksClosed()) return false;
if (!isInCurrentSprint()) return false;
return getRejectDate() != null;
}
public void reject() {
setRejectDate(Date.today());
}
public void fix() {
setRejectDate(null);
}
public String getEstimatedWorkAsString() {
Float work = getEstimatedWork();
if (work == null) return null;
if (work <= 0.5f) return work.toString();
return String.valueOf(work.intValue());
}
public String getEstimatedWorkWithUnit() {
String work = getEstimatedWorkAsString();
return work == null ? null : work + " " + getProject().getEffortUnit();
}
public List<RequirementEstimationVote> getEstimationVotes() {
return getDao().getRequirementEstimationVotesByRequirement(this);
}
public boolean containsWorkEstimationVotes() {
for (RequirementEstimationVote vote : getEstimationVotes()) {
if (vote.getEstimatedWork() != null) return true;
}
return false;
}
public RequirementEstimationVote getEstimationVote(User user) {
for (RequirementEstimationVote vote : getEstimationVotes()) {
if (vote.isUser(user)) return vote;
}
return null;
}
public void setVote(Float estimatedWork) {
RequirementEstimationVote vote = getEstimationVote(Scope.get().getComponent(Auth.class).getUser());
if (vote == null) throw new IllegalStateException("vote == null");
vote.setEstimatedWork(estimatedWork);
if (estimatedWork != null && isWorkEstimationVotingComplete()) activateWorkEstimationVotingShowoff();
}
public boolean isWorkEstimationVotingComplete() {
for (User user : getProject().getTeamMembers()) {
RequirementEstimationVote vote = getEstimationVote(user);
if (vote == null || vote.getEstimatedWork() == null) return false;
}
return true;
}
public void deactivateWorkEstimationVoting() {
setWorkEstimationVotingActive(false);
}
public void activateWorkEstimationVotingShowoff() {
setWorkEstimationVotingShowoff(true);
}
public String getTaskStatusLabel() {
int burned = Task.sumBurnedWork(getTasks());
int remaining = Task.sumRemainingWork(getTasks());
if (remaining == 0) return "100% completed, " + burned + " hrs burned";
int burnedPercent = Gwt.percent(burned + remaining, burned);
return burnedPercent + "% completed, " + remaining + " hrs left";
}
public void setEstimationBar(EstimationBar estimationBar) {
this.estimationBar = estimationBar;
}
public EstimationBar getEstimationBar() {
return estimationBar;
}
public boolean isValidForSprint() {
if (!isEstimatedWorkValid()) return false;
return true;
}
public boolean isEstimatedWorkValid() {
return !isDirty() && getEstimatedWork() != null;
}
public String getLongLabel() {
StringBuilder sb = new StringBuilder();
sb.append(getLabel());
if (!isEstimatedWorkValid()) sb.append(" [requires estimation]");
if (isInCurrentSprint()) sb.append(" [In Sprint]");
return sb.toString();
}
public boolean isInCurrentSprint() {
return isSprintSet() && getProject().isCurrentSprint(getSprint());
}
public String getReferenceAndLabel() {
return getReference() + " " + getLabel();
}
public String getReference() {
return REFERENCE_PREFIX + getNumber();
}
/**
* No tasks created yet.
*/
public boolean isPlanned() {
return !getTasks().isEmpty();
}
/**
* All tasks are done. Not closed yet.
*/
public boolean isTasksClosed() {
Collection<Task> tasks = getTasks();
if (tasks.isEmpty()) return false;
for (Task task : tasks) {
if (!task.isClosed()) return false;
}
return true;
}
/**
* Summary to show in the product backlog.
*/
public String getProductBacklogSummary() {
String summary = isDirty() ? "[dirty] " : "[not dirty] ";
if (isClosed()) return summary += "Closed.";
if (isTasksClosed()) return summary += "Done. Test required.";
if (getEstimatedWork() == null) return summary += "No effort estimated.";
if (!isSprintSet()) return summary += getEstimatedWorkWithUnit() + " to do. No sprint assigned.";
Sprint sprint = getSprint();
return summary += getEstimatedWorkWithUnit() + " to do in sprint " + sprint.getLabel() + ".";
}
/**
* Summary to show in the sprint backlog.
*/
public String getSprintBacklogSummary() {
if (isClosed()) return "Closed.";
if (!isPlanned()) return "Not planned yet.";
if (isTasksClosed()) return "Done. Test required.";
int taskCount = 0;
int openTaskCount = 0;
int effort = 0;
for (Task task : getTasks()) {
taskCount++;
if (!task.isClosed()) {
openTaskCount++;
effort += task.getRemainingWork();
}
}
return openTaskCount + " of " + taskCount + " Tasks open. About " + effort + " hours to do.";
}
public int getBurnedWorkInClosedTasks() {
return Task.sumBurnedWork(getClosedTasks());
}
public int getBurnedWork() {
return Task.sumBurnedWork(getTasks());
}
public int getBurnedWorkInClaimedTasks() {
return Task.sumBurnedWork(getClaimedTasks());
}
public int getRemainingWorkInClaimedTasks() {
return Task.sumRemainingWork(getClaimedTasks());
}
public int getRemainingWorkInUnclaimedTasks() {
return Task.sumRemainingWork(getUnclaimedTasks());
}
public int getRemainingWork() {
return Task.sumRemainingWork(getTasks());
}
public List<Task> getClaimedTasks() {
List<Task> ret = new ArrayList<Task>();
for (Task task : getTasks()) {
if (task.isOwnerSet() && !task.isClosed()) ret.add(task);
}
return ret;
}
public List<Task> getClaimedTasks(User owner) {
List<Task> ret = new ArrayList<Task>();
for (Task task : getTasks()) {
if (task.isOwner(owner) && !task.isClosed()) ret.add(task);
}
return ret;
}
public List<Task> getClosedTasks() {
List<Task> ret = new ArrayList<Task>();
for (Task task : getTasks()) {
if (task.isClosed()) ret.add(task);
}
return ret;
}
public List<Task> getUnclaimedTasks() {
List<Task> ret = new ArrayList<Task>();
for (Task task : getTasks()) {
if (task.isClosed() || task.isOwnerSet()) continue;
ret.add(task);
}
return ret;
}
public List<Task> getTasksBlockedBy(Impediment impediment) {
List<Task> ret = new ArrayList<Task>();
for (Task task : getTasks()) {
if (task.isImpediment(impediment)) ret.add(task);
}
return ret;
}
public List<Task> getTasks() {
return getDao().getTasksByRequirement(this);
}
public static int sumBurnedWork(Iterable<Requirement> requirements) {
int sum = 0;
for (Requirement requirement : requirements) {
sum += requirement.getBurnedWork();
}
return sum;
}
public Task createNewTask() {
Task task = new Task(this);
getDao().createTask(task);
updateTasksOrder();
return task;
}
public void deleteTask(Task task) {
getDao().deleteTask(task);
}
@Override
public boolean isEditable() {
if (isInCurrentSprint()) return false;
if (!getProject().isProductOwner(Scope.get().getComponent(Auth.class).getUser())) return false;
return true;
}
@Override
public String toHtml() {
return ScrumGwt.toHtml(getReference(), getLabel());
}
@Override
public String toString() {
return getReferenceAndLabel();
}
@Override
public Widget createForumItemWidget() {
return new HyperlinkWidget(new ShowEntityAction(this, getLabel()));
}
private void updateTasksOrder() {
List<Task> tasks = getTasks();
Collections.sort(tasks, getTasksOrderComparator());
updateTasksOrder(tasks);
}
public void updateTasksOrder(List<Task> tasks) {
setTasksOrderIds(Gwt.getIdsAsList(tasks));
}
public Comparator<Task> getTasksOrderComparator() {
if (tasksOrderComparator == null) tasksOrderComparator = new Comparator<Task>() {
@Override
public int compare(Task a, Task b) {
List<String> order = getTasksOrderIds();
int additional = order.size();
int ia = order.indexOf(a.getId());
if (ia < 0) {
ia = additional;
additional++;
}
int ib = order.indexOf(b.getId());
if (ib < 0) {
ib = additional;
additional++;
}
return ia - ib;
}
};
return tasksOrderComparator;
}
}