/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.dev;

import java.util.Arrays;

/**
 * Represents a GWT version.
 */
public final class GwtVersion implements Comparable<GwtVersion> {

  private static final int NO_NAG = 999;
  private static final String DEFAULT_NO_NAG_VERSION = "0.0." + NO_NAG;

  private static final int COMPONENT_COUNT = 3;

  /**
   *  Array of 3 integers.
   */
  private final int[] components = new int[COMPONENT_COUNT];

  /**
   * The suffix of the release, such as -ms1, -rc2, or random garbage.
   */
  private final String suffix;

  /**
   * Create a version that avoids any nagging -- "0.0.999".
   */
  public GwtVersion() {
    this(DEFAULT_NO_NAG_VERSION);
  }

  /**
   * Parse a version number as a string. An empty or null string are
   * explicitly allowed and are equivalent to "0.0.0".
   *
   * <p>Acceptable format:
   * <ul>
   * <li>prefix before first digit is ignored
   * <li>one or more digits or strings separated by a period
   * <li>optional release number suffix, such as -ms1, -rc3, etc.
   * <li>stops parsing at first space or dash
   * </ul>
   *
   * <p>The returned version always contains at least 3 components (padding with
   * "0" to 3 components) followed by a release number (which is always last).
   *
   * @param versionString GWT version in string form, ex: "2.1.0-rc2"
   * @throws NumberFormatException
   */
  public GwtVersion(String versionString) throws NumberFormatException {
    suffix = parse(versionString);
  }

  @Override
  public int compareTo(GwtVersion other) {
    for (int i = 0; i < COMPONENT_COUNT; ++i) {
      int c = components[i] - other.components[i];
      if (c != 0) {
        return c;
      }
    }
    return compareSuffixes(suffix, other.suffix);
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof GwtVersion)) {
      return false;
    }
    GwtVersion other = (GwtVersion) o;
    if (!Arrays.equals(components, other.components)) {
      return false;
    }
    return compareSuffixes(suffix, other.suffix) == 0;
  }

  /**
   * @return a copy of the array of version components, always exactly length 3.
   */
  public int[] getComponents() {
    // Avoid Arrays.copyOf since it was added in JDK1.6
    int[] returnVal = new int[COMPONENT_COUNT];
    System.arraycopy(components, 0, returnVal, 0, COMPONENT_COUNT);
    return returnVal;
  }

  /**
   * @return the suffix of this version.  Null indicates no suffix and that this
   * is a released version.
   */
  public String getSuffix() {
    return suffix;
  }

  @Override
  public int hashCode() {
    // all non-null suffixes are treated identically
    return Arrays.hashCode(components) * 2 + (suffix == null ? 0 : 1);
  }

  /**
   * @return true if this version is a special no-nag version (where the user
   * isn't notified that a newer version is available).  This is defined as any
   * version number with 999 in the third component.
   */
  public boolean isNoNagVersion() {
    return components[2] == NO_NAG;
  }

  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder();
    String prefix = "";
    for (int i = 0; i < COMPONENT_COUNT; ++i) {
      buf.append(prefix).append(components[i]);
      prefix = ".";
    }
    if (suffix != null) {
      buf.append(suffix);
    }
    return buf.toString();
  }

  /**
   * Compare two version number suffixes.  A null suffix is considered a
   * released version and comes after any with a suffix, and all non-null
   * suffixes are considered equal.
   *
   * @param suffix1
   * @param suffix2
   * @return negative if suffix1 < suffix2, positive if suffix2 > suffix1,
   *     or 0 if they are considered equal
   */
  private int compareSuffixes(String suffix1, String suffix2) {
    if (suffix1 == null) {
      return suffix2 == null ? 0 : 1;
    }
    if (suffix2 == null) {
      return -1;
    }
    return 0;
  }

  /**
   * Parse a string containing a GwtVersion.
   *
   * <p>Acceptable format:
   * <ul>
   * <li>prefix before first digit is ignored
   * <li>one or more digits or strings separated by a period (at most 3 sets of
   * digits)
   * <li>optional release number suffix, such as -ms1, -rc3, etc.
   * </ul>
   *
   * <p>The returned version always contains at least 3 components (padding with
   * "0" to 3 components) followed by a release number (which is always last).
   *
   * @param versionString GWT version in string form, ex: "2.1.0-rc2"
   * @return the trailing suffix, or null if none
   */
  private String parse(String versionString) {
    components[0] = components[1] = components[2] = 0;
    int len = versionString == null ? 0 : versionString.length();
    // Skip leading characters that are not digits to support a
    // non-numeric prefix on a version string.
    int index = 0;
    for (; index < len; ++index) {
      if (Character.isDigit(versionString.charAt(index))) {
        break;
      }
    }
    for (int component = 0; component < COMPONENT_COUNT; ++component) {
      int componentStart = index;
      while (index < len && Character.isDigit(versionString.charAt(index))) {
        ++index;
      }
      if (index > componentStart) {
        components[component] = Integer.parseInt(versionString.substring(
            componentStart, index));
      }
      if (index >= len || versionString.charAt(index) != '.') {
        break;
      }
      ++index;
    }
    return index < len ? versionString.substring(index) : null;
  }
}
