package org.ambraproject.wombat.util;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

import java.util.EnumSet;
import java.util.regex.Pattern;

/**
 * Utilities to transforming excerpts of NLM XML article text.
 * <p/>
 * There are several places where we have such excerpts (usually the extracted content of a single XML element) and wish
 * to display it as HTML. Properly, this is handled by an XSL transformation, which is extensible and customizable
 * through the theme system. This class contains some legacy Java kludges that do the same thing. (Forked from Ambra 2's
 * {@code org.ambraproject.util.ArticleFormattingDirective}.)
 * <p/>
 * All markup output from here has classes of the form {@code "nlm-*"}, in order to associate styling (or JavaScript,
 * etc.) specifically with elements that originated as NLM XML elements transformed in this way. This is in lieu of
 * allowing themes to place their own classes and identifiers (as would be possible in a theme-defined transformation).
 * <p/>
 * The implementation looks for XML tags with regexes, which is inherently brittle. XML is <a
 * href="http://stackoverflow.com/a/1732454">is not a regular language,</a> which makes it impossible to find certain
 * kinds of nested structures. We assume the use case excludes such problems. Beyond that, there are a ton of gotchas
 * around whitespace, optional elements, etc. This whole class is ripe for replacement with a real XML parser or
 * transformer.
 */
public class XmlExcerptTransformation {
  private XmlExcerptTransformation() {
    throw new AssertionError("Not instantiable");
  }

  private static interface TextReplacement {
    String replace(String input);
  }

  /**
   * NLM element types with no attributes and a common pattern of replacement. Assumes that two elements of the same
   * type will never be recursively nested.
   */
  private static enum SimpleElementType implements TextReplacement {
    // Enum names are (privately) used reflectively
    ITALIC("i"),
    BOLD("b"),
    MONOSPACE("span"),
    OVERLINE("span"),
    SC("span"),
    STRIKE("del"),
    UNDERLINE("span");

    private final Pattern pattern;
    private final String replacement;

    /**
     * @param htmlTag the HTML tag type to replace XML tags of this type
     */
    private SimpleElementType(String htmlTag) {
      String nlmXmlTag = name().toLowerCase();

      this.pattern = Pattern.compile("" +
          "\"(?:<|&lt;)" + nlmXmlTag + "(?:>|&gt;)" +
          "(.*?)" +
          "(?:<|&lt;)/" + nlmXmlTag + "(?:>|&gt;)\"");
      this.replacement = String.format("<%s class=\"nlm-%s\">$1</%s>",
          htmlTag, nlmXmlTag, htmlTag);
    }

    /**
     * Replace matched XML elements with HTML elements.
     *
     * @param input the XML text to search
     * @return the transformed text
     */
    @Override
    public String replace(String input) {
      return pattern.matcher(input).replaceAll(replacement);
    }
  }

  /**
   * Ad-hoc regexes from the legacy Ambra implementation.
   */
  private static enum LegacyKludge implements TextReplacement {
    NAMED_CONTENT(
        "<named-content(?:" +
            "(?:\\s+xmlns:xlink\\s*=\\s*\"http://www.w3.org/1999/xlink\"\\s*)|" +
            "(?:\\s+content-type\\s*=\\s*\"genus-species\"\\s*)|" +
            "(?:\\s+xlink:type\\s*=\\s*\"simple\"\\s*)" +
            ")*>(.*?)</named-content>",
        "<i class=\"nlm-named-content\">$1</i>"
    ),
    EMAIL(
        "<email(?:" +
            "(?:\\s+xmlns:xlink\\s*=\\s*\"http://www.w3.org/1999/xlink\"\\s*)|" +
            "(?:\\s+xlink:type\\s*=\\s*\"simple\"\\s*)" +
            ")*>(.*?)</email>",
        "<a class=\"nlm-email\" href=\"mailto:$1\">$1</a>"
    ),
    SEC_TITLE(
        "<sec id=\\\"st1\\\">[\n\t ]*<title ?/>", // What is special about id="st1"?
        "" // Just delete it
    );

    private final Pattern pattern;
    private final String replacement;

    private LegacyKludge(String pattern, String replacement) {
      this.pattern = Pattern.compile(pattern);
      this.replacement = replacement;
    }

    @Override
    public String replace(String input) {
      return pattern.matcher(input).replaceAll(replacement);
    }
  }

  private static final ImmutableSet<TextReplacement> REPLACEMENTS = ImmutableSet.<TextReplacement>builder()
      .addAll(EnumSet.allOf(SimpleElementType.class))
      .addAll(EnumSet.allOf(LegacyKludge.class))
      .build();

  /**
   * Transform NLM XML text to generic HTML. Applies a simplified transformation that looks only for XML elements in
   * common excerpts.
   *
   * @param text the XML text to search
   * @return the transformed HTML
   */
  public static String transform(String text) {
    for (TextReplacement replacement : REPLACEMENTS) {
      text = replacement.replace(text);
    }
    return text;
  }

}

@@ -74,19 +74,26 @@
 mmutable
-Set
+Collection
 ;%0Aimport
@@ -123,12 +123,21 @@
 ect.
-Sets
+ImmutableList
 ;%0A%0Ai
@@ -2466,24 +2466,336 @@
 ompile(%22%22 +%0A
+          // Not sure why we are checking for HTML-escaped %22&lt;%22 and %22&gt;%22 in what is supposed to be XML.%0A          // The legacy impl did it (only for %3Citalic%3E), so surely there was a special case at some point.%0A          // The special case likely doesn't exist on this platform, so investigate removing it.%0A
           %22%5C
@@ -4598,19 +4598,26 @@
 mmutable
-Set
+Collection
 %3CTextRep
@@ -4650,18 +4650,19 @@
 mmutable
-Se
+Lis
 t.%3CTextR

Add comment; style tweak