package org.ambraproject.rhino.view;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import org.ambraproject.models.Annotation;
import org.ambraproject.models.Article;
import org.ambraproject.rhino.view.article.ArticleVisibility;

import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * View of a comment, with nested views of all its children.
 */
public class AnnotationOutputView implements JsonOutputView {

  private final ArticleVisibility parentArticle;
  private final Annotation comment;
  private final ImmutableList<AnnotationOutputView> replies;

  private final int replyTreeSize;
  private final Date mostRecentActivity;

  private AnnotationOutputView(ArticleVisibility parentArticle,
                               Annotation comment,
                               List<AnnotationOutputView> replies,
                               int replyTreeSize, Date mostRecentActivity) {
    this.parentArticle = Objects.requireNonNull(parentArticle);
    this.comment = Objects.requireNonNull(comment);
    this.replies = ImmutableList.copyOf(replies);

    Preconditions.checkArgument(replyTreeSize >= 0);
    this.replyTreeSize = replyTreeSize;
    this.mostRecentActivity = Objects.requireNonNull(mostRecentActivity);
  }

  public static class Factory {
    private final ArticleVisibility parentArticle;
    private final Map<Long, List<Annotation>> commentsByParent;

    /**
     * @param parentArticle an article
     * @param comments      all comments belonging to the parent article
     */
    public Factory(Article parentArticle, Collection<Annotation> comments) {
      this.parentArticle = ArticleVisibility.create(parentArticle);
      this.commentsByParent = comments.stream()
          .filter(comment -> comment.getParentID() != null)
          .collect(Collectors.groupingBy(Annotation::getParentID));
    }

    /**
     * @param comment a comment belonging to this object's parent article
     * @return a view of the comment and all its children
     */
    public AnnotationOutputView buildView(Annotation comment) {
      List<Annotation> childObjects = commentsByParent.getOrDefault(comment.getID(), ImmutableList.of());
      List<AnnotationOutputView> childViews = childObjects.stream()
          .sorted(BY_DATE)
          .map(this::buildView) // recursion (terminal case is when childObjects is empty)
          .collect(Collectors.toList());

      int replyTreeSize = calculateReplyTreeSize(childViews);
      Date mostRecentActivity = findMostRecentActivity(comment, childViews);

      return new AnnotationOutputView(parentArticle, comment, childViews, replyTreeSize, mostRecentActivity);
    }

    private static int calculateReplyTreeSize(Collection<AnnotationOutputView> childViews) {
      return childViews.size() + childViews.stream().mapToInt(view -> view.replyTreeSize).sum();
    }

    private static Date findMostRecentActivity(Annotation comment, Collection<AnnotationOutputView> childViews) {
      return childViews.stream()
          .map(view -> view.mostRecentActivity)
          .max(Comparator.naturalOrder())
          .orElse(comment.getCreated());
    }
  }

  public static final Comparator<Annotation> BY_DATE = Comparator.comparing(Annotation::getCreated);

  @Override
  public JsonElement serialize(JsonSerializationContext context) {
    JsonObject serialized = context.serialize(comment).getAsJsonObject();
    serialized.remove("articleID");
    serialized.add("parentArticle", context.serialize(parentArticle));
    serialized.add("replyTreeSize", context.serialize(replyTreeSize));
    serialized.add("mostRecentActivity", context.serialize(mostRecentActivity));
    serialized.add("replies", context.serialize(replies));
    return serialized;
  }

}

@@ -195,16 +195,54 @@
 Object;%0A
+import com.google.gson.JsonPrimitive;%0A
 import c
@@ -3570,160 +3570,728 @@
 );%0A%0A
-  @Override%0A  public JsonElement serialize(JsonSerializationContext context) %7B%0A    JsonObject serialized = context.serialize(comment).getAsJsonObject();
+%0A  /**%0A   * If the named field is null or absent, replace it with an empty string.%0A   */%0A  private static void normalizeField(JsonObject object, String name) %7B%0A    JsonElement element = object.get(name);%0A    if (element == null %7C%7C element.isJsonNull()) %7B%0A      object.add(name, EMPTY_STRING);%0A    %7D%0A  %7D%0A%0A  private static final JsonElement EMPTY_STRING = new JsonPrimitive(%22%22);%0A%0A  @Override%0A  public JsonElement serialize(JsonSerializationContext context) %7B%0A    JsonObject serialized = context.serialize(comment).getAsJsonObject();%0A    normalizeField(serialized, %22title%22);%0A    normalizeField(serialized, %22body%22);%0A    normalizeField(serialized, %22highlightedText%22);%0A    normalizeField(serialized, %22competingInterestBody%22);%0A
 %0A   

Normalize null comment fields as empty strings