package com.hubspot.singularity;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.annotation.Nonnull;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Iterables;

public class SingularityTaskHistoryUpdate extends SingularityTaskIdHolder implements Comparable<SingularityTaskHistoryUpdate> {

  private final long timestamp;
  private final ExtendedTaskState taskState;
  private final Optional<String> statusMessage;
  private final Optional<String> statusReason;
  private final Set<SingularityTaskHistoryUpdate> previous;

  public enum SimplifiedTaskState {
    UNKNOWN, WAITING, RUNNING, DONE
  }

  public static Optional<SingularityTaskHistoryUpdate> getUpdate(final Iterable<SingularityTaskHistoryUpdate> updates, final ExtendedTaskState taskState) {
    return Iterables.tryFind(updates, new Predicate<SingularityTaskHistoryUpdate>() {
      @Override
      public boolean apply(@Nonnull SingularityTaskHistoryUpdate input) {
        return input.getTaskState() == taskState;
      }
    });
  }

  public static SimplifiedTaskState getCurrentState(Iterable<SingularityTaskHistoryUpdate> updates) {
    SimplifiedTaskState state = SimplifiedTaskState.UNKNOWN;

    if (updates == null) {
      return state;
    }

    for (SingularityTaskHistoryUpdate update : updates) {
      if (update.getTaskState().isDone()) {
        return SimplifiedTaskState.DONE;
      } else if (update.getTaskState() == ExtendedTaskState.TASK_RUNNING) {
        state = SimplifiedTaskState.RUNNING;
      } else if (state == SimplifiedTaskState.UNKNOWN) {
        state = SimplifiedTaskState.WAITING;
      }
    }

    return state;
  }

  public SingularityTaskHistoryUpdate(SingularityTaskId taskId, long timestamp, ExtendedTaskState taskState, Optional<String> statusMessage, Optional<String> statusReason) {
    this(taskId, timestamp, taskState, statusMessage, statusReason, Collections.<SingularityTaskHistoryUpdate>emptySet());
  }

  @JsonCreator
  public SingularityTaskHistoryUpdate(@JsonProperty("taskId") SingularityTaskId taskId,
                                      @JsonProperty("timestamp") long timestamp,
                                      @JsonProperty("taskState") ExtendedTaskState taskState,
                                      @JsonProperty("statusMessage") Optional<String> statusMessage,
                                      @JsonProperty("statusReason") Optional<String> statusReason,
                                      @JsonProperty("previous") Set<SingularityTaskHistoryUpdate> previous) {
    super(taskId);

    this.timestamp = timestamp;
    this.taskState = taskState;
    this.statusMessage = statusMessage;
    this.statusReason = statusReason;
    this.previous = previous != null ? previous : Collections.<SingularityTaskHistoryUpdate>emptySet();
  }

  public SingularityTaskHistoryUpdate withPrevious(SingularityTaskHistoryUpdate previousUpdate) {
    Set<SingularityTaskHistoryUpdate> newPreviousUpdates = getFlattenedPreviousUpdates(this);
    newPreviousUpdates.add(previousUpdate);
    newPreviousUpdates.addAll(getFlattenedPreviousUpdates(previousUpdate));
    return new SingularityTaskHistoryUpdate(getTaskId(), timestamp, taskState, statusMessage, statusReason, newPreviousUpdates);
  }

  private Set<SingularityTaskHistoryUpdate> getFlattenedPreviousUpdates(SingularityTaskHistoryUpdate update) {
    Set<SingularityTaskHistoryUpdate> previousUpdates = new HashSet<>(update.getPrevious());
    for (SingularityTaskHistoryUpdate preivousUpdate : update.getPrevious()) {
      previousUpdates.addAll(getFlattenedPreviousUpdates(preivousUpdate));
    }
    return previousUpdates;
  }

  @Override
  public int compareTo(SingularityTaskHistoryUpdate o) {
    return ComparisonChain.start()
        .compare(taskState.ordinal(), o.getTaskState().ordinal())
        .compare(timestamp, o.getTimestamp())
        .compare(o.getTaskId().getId(), getTaskId().getId())
        .result();
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(getTaskId(), timestamp, taskState, statusMessage, statusReason);
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if ((other == null) || (other.getClass() != this.getClass())) {
      return false;
    }

    SingularityTaskHistoryUpdate that = (SingularityTaskHistoryUpdate) other;

    return Objects.equal(this.getTaskId(), that.getTaskId())
        && Objects.equal(this.timestamp, that.timestamp)
        && Objects.equal(this.taskState, that.taskState)
        && Objects.equal(this.statusMessage, that.statusMessage)
        && Objects.equal(this.statusReason, that.statusReason);
  }

  public long getTimestamp() {
    return timestamp;
  }

  public ExtendedTaskState getTaskState() {
    return taskState;
  }

  public Optional<String> getStatusMessage() {
    return statusMessage;
  }

  public Optional<String> getStatusReason() {
    return statusReason;
  }

  public Set<SingularityTaskHistoryUpdate> getPrevious() {
    return previous;
  }

  @Override public String toString() {
    return "SingularityTaskHistoryUpdate[" +
        "timestamp=" + timestamp +
        ", taskState=" + taskState +
        ", statusMessage=" + statusMessage +
        ", statusReason=" + statusReason +
        ", previous=" + previous +
        ']';
  }
}

@@ -3320,19 +3320,37 @@
 usUpdate
+.withoutPrevious()
 );%0A
-
     newP
@@ -3732,36 +3732,16 @@
 shSet%3C%3E(
-update.getPrevious()
 );%0A    f
@@ -3810,24 +3810,85 @@
 evious()) %7B%0A
+      previousUpdates.add(preivousUpdate.withoutPrevious());%0A
       previo
@@ -3952,24 +3952,24 @@
 te));%0A    %7D%0A
-
     return p
@@ -3981,32 +3981,258 @@
 usUpdates;%0A  %7D%0A%0A
+  public SingularityTaskHistoryUpdate withoutPrevious() %7B%0A    return new SingularityTaskHistoryUpdate(getTaskId(), timestamp, taskState, statusMessage, statusReason, Collections.%3CSingularityTaskHistoryUpdate%3EemptySet());%0A  %7D%0A%0A
   @Override%0A  pu

no duplicates