package io.ebean.migration;

import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The version of a migration used so that migrations are processed in order.
 */
public class MigrationVersion implements Comparable<MigrationVersion> {

  private static final Logger logger = LoggerFactory.getLogger(MigrationVersion.class);
  
  private static final int[] REPEAT_ORDERING = {Integer.MAX_VALUE};

  private static final boolean[] REPEAT_UNDERSCORES = {false};

  /**
   * The raw version text.
   */
  private final String raw;

  /**
   * The ordering parts.
   */
  private final int[] ordering;

  private final boolean[] underscores;

  private final String comment;

  /**
   * Construct for "repeatable" version.
   */
  private MigrationVersion(String raw, String comment) {
    this.raw = raw;
    this.comment = comment;
    this.ordering = REPEAT_ORDERING;
    this.underscores = REPEAT_UNDERSCORES;
  }

  /**
   * Construct for "normal" version.
   */
  private MigrationVersion(String raw, int[] ordering, boolean[] underscores, String comment) {
    this.raw = raw;
    this.ordering = ordering;
    this.underscores = underscores;
    this.comment = comment;
  }

  /**
   * Return true if this is a "repeatable" version.
   */
  public boolean isRepeatable() {
    return ordering == REPEAT_ORDERING;
  }

  /**
   * Return the full version.
   */
  public String getFull() {
    return raw;
  }

  public String toString() {
    return raw;
  }

  /**
   * Return the version comment.
   */
  public String getComment() {
    return comment;
  }

  /**
   * Return the version in raw form.
   */
  public String getRaw() {
    return raw;
  }

  /**
   * Return the trimmed version excluding version comment and un-parsable string.
   */
  public String asString() {
    return formattedVersion(false, false);
  }

  /**
   * Return the trimmed version with any underscores replaced with '.'
   */
  public String normalised() {
    return formattedVersion(true, false);
  }

  /**
   * Return the next version based on this version.
   */
  public String nextVersion() {
    return formattedVersion(false, true);
  }

  /**
   * Returns the version part of the string.
   * <p>
   * Normalised means always use '.' delimiters (no underscores).
   * NextVersion means bump/increase the last version number by 1.
   */
  private String formattedVersion(boolean normalised, boolean nextVersion) {

    if (ordering == REPEAT_ORDERING) {
      return "R";
    }
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < ordering.length; i++) {
      if (i < ordering.length - 1) {
        sb.append(ordering[i]);
        if (normalised) {
          sb.append('.');
        } else {
          sb.append(underscores[i] ? '_' : '.');
        }
      } else {
        sb.append((nextVersion) ? ordering[i] + 1 : ordering[i]);
      }
    }
    return sb.toString();
  }

  @Override
  public int compareTo(MigrationVersion other) {

    int otherLength = other.ordering.length;
    for (int i = 0; i < ordering.length; i++) {
      if (i >= otherLength) {
        // considered greater
        return 1;
      }
      if (ordering[i] != other.ordering[i]) {
        return (ordering[i] > other.ordering[i]) ? 1 : -1;
      }
    }
    if (ordering.length < otherLength) {
      return -1;
    }
    return comment.compareTo(other.comment);
  }

  /**
   * Parse the raw version string and just return the leading version number;
   */
  public static String trim(String raw) {
    return parse(raw).asString();
  }

  /**
   * Parse the raw version string into a MigrationVersion.
   */
  public static MigrationVersion parse(String raw) {

    if (raw.startsWith("V") || raw.startsWith("v")) {
      raw = raw.substring(1);
    }

    String comment = "";
    String value = raw;
    int commentStart = raw.indexOf("__");
    if (commentStart > -1) {
      // trim off the trailing comment
      comment = raw.substring(commentStart + 2);
      value = value.substring(0, commentStart);
    }

    value = value.replace('_', '.');

    String[] sections = value.split("[\\.-]");

    if (sections[0].startsWith("R") || sections[0].startsWith("r")) {
      // a "repeatable" version (does not have a version number)
      return new MigrationVersion(raw, comment);
    }

    boolean[] underscores = new boolean[sections.length];
    int[] ordering = new int[sections.length];

    int delimiterPos = 0;
    int stopIndex = 0;
    for (int i = 0; i < sections.length; i++) {
      try {
        ordering[i] = Integer.parseInt(sections[i]);
        stopIndex++;

        delimiterPos += sections[i].length();
        underscores[i] = (delimiterPos < raw.length() - 1 && raw.charAt(delimiterPos) == '_');
        delimiterPos++;
      } catch (NumberFormatException e) {
        // stop parsing
        logger.warn("The migrationscript '{}' contains non numeric version part. "
            + "This may lead to misordered version scripts. NumberFormatException {}", raw, e.getMessage());
        break;
      }
    }

    int[] actualOrder = Arrays.copyOf(ordering, stopIndex);
    boolean[] actualUnderscores = Arrays.copyOf(underscores, stopIndex);

    return new MigrationVersion(raw, actualOrder, actualUnderscores, comment);
  }

}

@@ -353,18 +353,16 @@
 class);%0A
-  
 %0A  priva
@@ -398,16 +398,93 @@
 ORDERING
+_MIN = %7BInteger.MIN_VALUE%7D;%0A%0A  private static final int%5B%5D REPEAT_ORDERING_MAX
  = %7BInte
@@ -873,32 +873,46 @@
 , String comment
+, boolean init
 ) %7B%0A    this.raw
@@ -966,16 +966,23 @@
 dering =
+ init ?
  REPEAT_
@@ -985,24 +985,50 @@
 EAT_ORDERING
+_MIN : REPEAT_ORDERING_MAX
 ;%0A    this.u
@@ -1465,16 +1465,55 @@
 ORDERING
+_MIN %7C%7C ordering == REPEAT_ORDERING_MAX
 ;%0A  %7D%0A%0A 
@@ -2614,35 +2614,22 @@
 if (
-ordering == REPEAT_ORDERING
+isRepeatable()
 ) %7B%0A
@@ -4454,24 +4454,226 @@
 raw, comment
+, false);%0A    %7D%0A%0A    if (sections%5B0%5D.startsWith(%22I%22) %7C%7C sections%5B0%5D.startsWith(%22i%22)) %7B%0A      // this script will be executed before all other scripts%0A      return new MigrationVersion(raw, comment, true
 );%0A    %7D%0A%0A  

Support for repeatable scripts, that are executed first (#50)