package org.gbif.api.model.common;

import java.io.IOException;
import java.net.URI;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.annotate.JsonDeserialize;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.map.ser.std.SerializerBase;

/**
 * Class representing a single Digital Object Identifier (DOI) breaking it down to a prefix and suffix.
 * For the syntax of DOI names see the <a href="http://www.doi.org/doi_handbook/2_Numbering.html#2.2">DOI Handbook</a>.
 * All parsing is case insensitive and resulting components will all be upper cased.
 */
@JsonSerialize(using = DOI.Serializer.class)
@JsonDeserialize(using = DOI.Deserializer.class)
public class DOI {
  public static final String GBIF_PREFIX = "10.15468";
  private static final Pattern HTTP = Pattern.compile("^https?://(dx\\.)?doi\\.org/"
                                                      + "(urn:)?(doi:)?", Pattern.CASE_INSENSITIVE);
  private static final Pattern PARSER = Pattern.compile("^(?:urn:)?(?:doi:)?"           // optional
                                                        + "(10(?:\\.[0-9]+)+)"
                                                        + "/(.+)$", Pattern.CASE_INSENSITIVE);

  private static String RESOLVER = "http://doi.org/";
  private static String SCHEME = "doi:";
  private String prefix;
  private String suffix;

  /**
   * Returns true only if the source can be parsed into a DOI.
   */
  public static boolean isParsable(String source) {
    if (!Strings.isNullOrEmpty(source)) {
      return PARSER.matcher(decodeUrl(source)).find();
    }
    return false;
  }

  /**
   * Do not use this constructor.
   * Required by JAXB to marshall this object without introducing adapters.
   */
  public DOI() {
  }

  /**
   * Parses a simple DOI string of various forms incl URN, URL or plain DOI names.
   * @param doi the full simple DOI string
   * @throws java.lang.IllegalArgumentException if invalid DOI string is passed
   */
  public DOI(String doi) {
    Preconditions.checkNotNull(doi, "DOI required");
    Matcher m = PARSER.matcher(decodeUrl(doi));
    if (m.find()) {
      this.prefix = m.group(1).toUpperCase();
      this.suffix = m.group(2).toUpperCase();
    } else {
      throw new IllegalArgumentException(doi + " is not a valid DOI");
    }
  }

  /**
   * Parses a simple DOI string of various forms incl URN, URL or plain DOI names.
   * @param prefix a simple DOI prefix starting with 10.
   * @param suffix arbitrary suffix part of the DOI
   * @throws java.lang.IllegalArgumentException if invalid DOI prefix is given
   */
  public DOI(String prefix, String suffix) {
    this.prefix = Preconditions.checkNotNull(prefix, "DOI prefix required").toUpperCase();
    Preconditions.checkArgument(prefix.startsWith("10."));
    this.suffix = Preconditions.checkNotNull(suffix, "DOI suffix required").toUpperCase();
  }

  /**
   * If the doi is encoded as a URL this method strips the resolver and decodes the URL encoded string entities.
   */
  private static String decodeUrl(String doi) {
    Matcher m = HTTP.matcher(doi);
    if (m.find()) {
      // strip resolver incl potentially starting paths using a badly encoded urn:doi
      // (the colon would need to be encoded in a proper URL)
      doi = m.replaceFirst("");
      // now decode the URL path, we cannot possibly have query parameters or anchors as the DOIs encoded as a URL
      // will just be a path
      return URI.create(doi).getPath();
    }
    return doi;
  }

  public String getPrefix() {
    return prefix;
  }

  public void setPrefix(String prefix) {
    this.prefix = prefix;
  }

  public String getSuffix() {
    return suffix;
  }

  public void setSuffix(String suffix) {
    this.suffix = suffix;
  }

  /**
   * See <a href="http://www.doi.org/doi_handbook/2_Numbering.html#2.6">DOI Hanbook, Visual presentation and other representation of DOI names</a>.
   * @return the resolved DOI using http://dx.doi.org
   */
  public URI getUrl() {
    return URI.create(RESOLVER + prefix + '/' + suffix);
  }

  /**
   * @return the pure DOI name without any initial scheme name starting with the prefix, i.e. 10.
   */
  public String getDoiName() {
    return prefix + '/' + suffix;
  }

  @Override
  public String toString() {
    return SCHEME + prefix + '/' + suffix;
  }

  @Override
  public int hashCode() {
    return Objects.hash(prefix, suffix);
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null || getClass() != obj.getClass()) {
      return false;
    }
    final DOI other = (DOI) obj;
    // prefix and suffix are always upper cased so we can do simple equals here
    return Objects.equals(this.prefix, other.prefix) && Objects.equals(this.suffix, other.suffix);
  }



  /**
   * Serializes a DOI as doi name with a doi: scheme.
   * For example doi:10.1038/nature.2014.16460
   */
  public static class Serializer extends SerializerBase<DOI> {

    public Serializer() {
      super(DOI.class);
    }

    @Override
    public void serialize(DOI value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
      jgen.writeString(value.toString());
    }
  }

  /**
   * Deserializes a DOI from various string based formats.
   * See DOI constructor for details.
   */
  public static class Deserializer extends JsonDeserializer<DOI> {

    @Override
    public DOI deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
      if (jp != null && jp.getTextLength() > 0) {
        return new DOI(jp.getText());
      }
      return null;
    }
  }

}

@@ -1099,62 +1099,291 @@
 I %7B%0A
-  public static final String GBIF_PREFIX = %2210.15468%22;
+%0A  /**%0A   * The DOI prefix registered with DataCite to be used by GBIF issued production DOIs.%0A   */%0A  public static final String GBIF_PREFIX = %2210.15468%22;%0A%0A  /**%0A   * A DOI prefix provided by DataCite to be used in tests.%0A   */%0A  public static final String TEST_PREFIX = %2210.5072%22;%0A
 %0A  p

Add the datacite DOI test prefix as a constant to DOI