package org.ambraproject.rhino.content;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import org.ambraproject.models.Article;
import org.ambraproject.models.Syndication;
import org.ambraproject.rhino.util.JsonAdapterUtil;
import org.ambraproject.service.article.NoSuchArticleIdException;
import org.ambraproject.service.syndication.SyndicationService;

import java.lang.reflect.Type;
import java.util.Collection;

/**
 * A view of an article for printing to JSON. When serialized to a JSON object, it should contain all the same fields as
 * a default dump of the article, plus a few added or augmented.
 */
public class ArticleOutputView {

  /**
   * The state constants given in {@link Article}, with names chosen for this API.
   */
  private static enum PublicationState {
    PUBLISHED(Article.STATE_ACTIVE),
    INGESTED(Article.STATE_UNPUBLISHED),
    DISABLED(Article.STATE_DISABLED);

    private final int constant;

    private PublicationState(int constant) {
      this.constant = constant;
    }

    public static final ImmutableMap<Integer, PublicationState> BY_CONSTANT =
        Maps.uniqueIndex(Iterators.forArray(values()), new Function<PublicationState, Integer>() {
          @Override
          public Integer apply(PublicationState input) {
            return input.constant;
          }
        });
  }

  private final Article article;
  private final ImmutableCollection<Syndication> syndications;

  private ArticleOutputView(Article article, Collection<Syndication> syndications) {
    this.article = Preconditions.checkNotNull(article);
    this.syndications = ImmutableList.copyOf(syndications);
  }

  public static ArticleOutputView create(Article article, SyndicationService syndicationService) {
    Collection<Syndication> syndications = null;
    try {
      syndications = syndicationService.getSyndications(article.getDoi());
    } catch (NoSuchArticleIdException e) {
      // Fall through and let it be set in the if-null block
    }
    if (syndications == null) {
      syndications = ImmutableList.of();
    }
    return new ArticleOutputView(article, syndications);
  }

  /**
   * Adapter object that produces the desired JSON output from a view instance.
   */
  public static final JsonSerializer<ArticleOutputView> SERIALIZER = new JsonSerializer<ArticleOutputView>() {

    @Override
    public JsonElement serialize(ArticleOutputView src, Type typeOfSrc, JsonSerializationContext context) {
      Article article = src.article;
      JsonObject serialized = new JsonObject();

      PublicationState pubState = PublicationState.BY_CONSTANT.get(article.getState());
      serialized.addProperty("doi", article.getDoi()); // Force it to be printed first, for human-friendliness
      serialized.addProperty("state", pubState.name().toLowerCase()); // Instead of the int value
      serialized.add("syndications", serializeSyndications(src.syndications, context));

      JsonObject baseJson = context.serialize(article).getAsJsonObject();
      serialized = JsonAdapterUtil.copyWithoutOverwriting(baseJson, serialized);

      return serialized;
    }

    /**
     * Represent the list of syndications as an object whose keys are the syndication targets.
     */
    private JsonElement serializeSyndications(Collection<Syndication> syndications, JsonSerializationContext context) {
      JsonObject syndicationsByTarget = new JsonObject();
      for (Syndication syndication : syndications) {
        JsonObject syndicationJson = context.serialize(syndication).getAsJsonObject();

        // Exclude redundant members
        syndicationJson.remove("doi");
        syndicationJson.remove("target");

        syndicationsByTarget.add(syndication.getTarget(), syndicationJson);
      }
      return syndicationsByTarget;
    }

  };

}

@@ -64,24 +64,29 @@
 on.base.
-Func
+Precondi
 tion
+s
 ;%0Aimport
@@ -104,34 +104,38 @@
 .common.
-base.Preconditions
+collect.ImmutableBiMap
 ;%0Aimport
@@ -234,138 +234,8 @@
 st;%0A
-import com.google.common.collect.ImmutableMap;%0Aimport com.google.common.collect.Iterators;%0Aimport com.google.common.collect.Maps;%0A
 impo
@@ -1004,14 +1004,20 @@
 cle%7D
-, with
+ mapped onto
  nam
@@ -1067,276 +1067,286 @@
 tic 
-enum PublicationState %7B%0A    PUBLISHED(Article.STATE_ACTIVE),%0A    INGESTED(Article.STATE_UNPUBLISHED),%0A    DISABLED(Article.STATE_DISABLED);%0A%0A    private final int constant;%0A%0A    private PublicationState(int constant) %7B%0A      this.constant = constant;%0A    %7D%0A%0A    public
+final ImmutableBiMap%3CInteger, String%3E PUBLICATION_STATE_CONSTANTS = ImmutableBiMap.%3CInteger, String%3Ebuilder()%0A      .put(Article.STATE_ACTIVE, %22published%22)%0A      .put(Article.STATE_UNPUBLISHED, %22ingested%22)%0A      .put(Article.STATE_DISABLED, %22disabled%22)%0A      .build();%0A  private
  sta
@@ -1368,291 +1368,95 @@
 able
+Bi
 Map%3C
-Integer, PublicationState%3E BY_C
+String, Integer%3E PUBLICATI
 ON
+_
 STA
-NT =%0A        Maps.uniqueIndex(Iterators.forArray(values()), new Function%3CPublicationState, Integer%3E() %7B%0A          @Override%0A          public Integer apply(PublicationState input) %7B%0A            return input.constant;%0A          %7D%0A        %7D);%0A  %7D
+TE_NAMES = PUBLICATION_STATE_CONSTANTS.inverse();
 %0A%0A  
@@ -2654,97 +2654,8 @@
 t();
-%0A%0A      PublicationState pubState = PublicationState.BY_CONSTANT.get(article.getState());
 %0A   
@@ -2766,105 +2766,447 @@
 ess%0A
+%0A
       
-serialized.addProperty(%22state%22, pubState.name().toLowerCase()); // Instead of the int value
+int articleState = article.getState();%0A      String pubState = PUBLICATION_STATE_CONSTANTS.get(articleState);%0A      if (pubState == null) %7B%0A        String message = String.format(%22Article.state field has unexpected value (%25d). Expected one of: %25s%22,%0A            articleState, PUBLICATION_STATE_CONSTANTS.keySet().toString());%0A        throw new IllegalStateException(message);%0A      %7D%0A      serialized.addProperty(%22state%22, pubState);%0A
 %0A   

Simplify publication state representation; add validation