package org.ambraproject.wombat.service;

import com.google.common.base.Strings;
import org.ambraproject.wombat.util.DoiSchemeStripper;

import javax.xml.bind.DatatypeConverter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CitationDownloadServiceImpl implements CitationDownloadService {

  /**
   * Return the fields of the author name, separated by commas, in a particular order.
   * <p>
   * Both the RIS and BibTex formats coincidentally use similar formats of the three name fields delimited by commas,
   * but place the fields in a different order.
   */
  private static String formatAuthorName(Map<String, String> authorData, String... fieldOrder) {
    return Stream.of(fieldOrder)
        .map((String fieldKey) -> authorData.get(fieldKey))
        .filter((String namePart) -> !Strings.isNullOrEmpty(namePart))
        .collect(Collectors.joining(", "));
  }


  private static StringBuilder appendRisCitationLine(StringBuilder builder, String key, String value) {
    return builder.append(Objects.requireNonNull(key)).append("  - ").append(Strings.nullToEmpty(value)).append('\n');
  }

  private static String formatDateForRis(Map<String, ?> articleMetadata) {
    String dateString = (String) articleMetadata.get("date");
    if (dateString == null) return "0000";
    Calendar date = DatatypeConverter.parseDateTime(dateString);
    return new SimpleDateFormat("YYYY/MM/dd").format(date.getTime());
  }

  @Override
  public String buildRisCitation(Map<String, ?> articleMetadata) {
    StringBuilder citation = new StringBuilder();
    appendRisCitationLine(citation, "TY", "JOUR");
    appendRisCitationLine(citation, "T1", (String) articleMetadata.get("title"));

    List<Map<String, String>> authors = (List<Map<String, String>>) articleMetadata.get("authors");
    for (Map<String, String> author : authors) {
      appendRisCitationLine(citation, "A1", formatAuthorName(author, "surnames", "givenNames", "suffix"));
    }
    List<String> collaborativeAuthors = (List<String>) articleMetadata.get("collaborativeAuthors");
    for (String collaborativeAuthor : collaborativeAuthors) {
      appendRisCitationLine(citation, "A1", collaborativeAuthor);
    }

    appendRisCitationLine(citation, "Y1", formatDateForRis(articleMetadata));
    appendRisCitationLine(citation, "N2", getSummaryForRis(articleMetadata));
    appendRisCitationLine(citation, "JF", (String) articleMetadata.get("journal"));
    appendRisCitationLine(citation, "JA", (String) articleMetadata.get("journal"));
    appendRisCitationLine(citation, "VL", (String) articleMetadata.get("volume"));
    appendRisCitationLine(citation, "IS", (String) articleMetadata.get("issue"));
    appendRisCitationLine(citation, "UR", (String) articleMetadata.get("url"));
    appendRisCitationLine(citation, "SP", (String) articleMetadata.get("eLocationId"));
    appendRisCitationLine(citation, "EP", null); // Always blank (copied from legacy implementation)
    appendRisCitationLine(citation, "PB", (String) articleMetadata.get("publisherName"));
    appendRisCitationLine(citation, "M3", "doi:" + DoiSchemeStripper.strip((String) articleMetadata.get("doi")));
    appendRisCitationLine(citation, "ER", null); // Always blank (copied from legacy implementation)

    return citation.toString();
  }

  private String getSummaryForRis(Map<String, ?> articleMetadata) {
    return "[THIS IS NOT IMPLEMENTED!]"; // TODO
  }


  private static enum BibtexField {
    AUTHOR("author", null) {
      @Override
      protected String extractValue(Map<String, ?> articleMetadata) {
        List<Map<String, String>> authors = (List<Map<String, String>>) articleMetadata.get("authors");
        Stream<String> formattedAuthors = authors.stream().map(authorData ->
            formatAuthorName(authorData, "surnames", "suffix", "givenNames"));
        List<String> collaborativeAuthors = (List<String>) articleMetadata.get("collaborativeAuthors");
        return Stream.concat(formattedAuthors, collaborativeAuthors.stream())
            .collect(Collectors.joining(" AND "));
      }
    },
    JOURNAL("journal", "journal"),
    PUBLISHER("publisher", "publisherName"),
    TITLE("title", "title"),
    YEAR("year", null) {
      @Override
      protected String extractValue(Map<String, ?> articleMetadata) {
        String formattedYear = extractFromDate(articleMetadata, new SimpleDateFormat("YYYY"));
        return (formattedYear == null) ? "0000" : formattedYear;
      }
    },
    MONTH("month", null) {
      @Override
      protected String extractValue(Map<String, ?> articleMetadata) {
        return extractFromDate(articleMetadata, new SimpleDateFormat("MM"));
      }
    },
    VOLUME("volume", "volume"),
    URL("url", "url"),
    PAGES("pages", "pages"),
    ABSTRACT("abstract", null) {
      @Override
      protected String extractValue(Map<String, ?> articleMetadata) {
        return "[THIS IS NOT IMPLEMENTED!]"; // TODO: Get abstract from article document and format as HTML
      }
    },
    NUMBER("number", "issue"),
    DOI("doi", "doi");

    private final String citationKey;
    private final String metadataKey;

    BibtexField(String citationKey, String metadataKey) {
      this.citationKey = Objects.requireNonNull(citationKey);
      this.metadataKey = metadataKey; // may be null if extractValue is overridden
    }

    protected String extractValue(Map<String, ?> articleMetadata) {
      return (String) articleMetadata.get(Objects.requireNonNull(metadataKey));
    }

    private static String extractFromDate(Map<String, ?> articleMetadata, DateFormat dateFormat) {
      String dateString = (String) articleMetadata.get("date");
      if (dateString == null) return null;
      Calendar date = DatatypeConverter.parseDateTime(dateString);
      return dateFormat.format(date.getTime());
    }
  }

  @Override
  public String buildBibtexCitation(Map<String, ?> articleMetadata) {
    String doiName = DoiSchemeStripper.strip((String) articleMetadata.get("doi"));
    StringBuilder result = new StringBuilder();
    result.append("@article{").append(doiName).append(",\n");

    result.append(EnumSet.allOf(BibtexField.class).stream()
        .map(field -> String.format("    %s = {%s}",
            field.citationKey, Strings.nullToEmpty(field.extractValue(articleMetadata))))
        .collect(Collectors.joining(",\n")));
    result.append("\n}");

    return result.toString();
  }

}


@@ -2524,40 +2524,50 @@
 2%22, 
-getSummaryForRis(articleMetadata
+(String) articleMetadata.get(%22description%22
 ));%0A
@@ -3515,130 +3515,8 @@
  %7D%0A%0A
-  private String getSummaryForRis(Map%3CString, ?%3E articleMetadata) %7B%0A    return %22%5BTHIS IS NOT IMPLEMENTED!%5D%22; // TODO%0A  %7D%0A%0A
 %0A  p
@@ -4884,223 +4884,22 @@
 t%22, 
-null) %7B%0A      @Override%0A      protected String extractValue(Map%3CString, ?%3E articleMetadata) %7B%0A        return %22%5BTHIS IS NOT IMPLEMENTED!%5D%22; // TODO: Get abstract from article document and format as HTML%0A      %7D%0A    %7D
+%22description%22)
 ,%0A  

Use article's description field for citation abstracts