package arcs.api;

import java.util.Arrays;

/**
 * An immutable object consisting of two components: a root, and an idTree. The root is the session
 * ID from the particular session in which the ID was constructed (see the IdGenerator class). The
 * idTree is a list of subcomponents, forming a hierarchy of IDs (child IDs are created by appending
 * subcomponents to their parent ID"s idTree).
 */
// copied from id.ts
public class Id {
  /** The Session ID of the session during which the ID got created. See IdGenerator class. */
  final String root;

  /** The components of the idTree. */
  final String[] idTree;

  /** Protected constructor. Use IdGenerator to create new IDs instead. */
  protected Id(String root, String[] idTree) {
    this.root = root;
    this.idTree = idTree;
  }

  /** Creates a new ID. Use IdGenerator to create new IDs instead. */
  static Id newIdInternal(String root, String[] idTree) {
    return new Id(root, idTree);
  }

  /** Parses a string representation of an ID (see toString). */
  public static Id fromString(String str) {
    String[] bits = str.split(":");

    if (bits[0].startsWith("!")) {
      String root = bits[0].substring(1);
      String[] idTree =
          Arrays.stream(Arrays.copyOfRange(bits, 1, bits.length))
              .filter(component -> component.length() > 0)
              .toArray(String[]::new);
      return new Id(root, idTree);
    } else {
      return new Id("", bits);
    }
  }

  /** Returns the full ID string. */
  @Override
  public String toString() {
    return "!" + root + ":" + String.join(":", idTree);
  }

  /** Returns the idTree as as string (without the root). */
  String idTreeAsString() {
    return String.join(":", idTree);
  }

  @Override
  public int hashCode() {
    return toString().hashCode();
  }

  @Override
  public boolean equals(Object other) {
    if (!(other instanceof Id)) {
      return false;
    }

    Id id = (Id) other;

    if (!id.root.equals(this.root) || id.idTree.length != this.idTree.length) {
      return false;
    }
    for (int i = 0; i < id.idTree.length; i++) {
      if (!id.idTree[i].equals(this.idTree[i])) {
        return false;
      }
    }
    return true;
  }
}

@@ -36,16 +36,64 @@
 .Arrays;
+%0Aimport java.util.Date;%0Aimport java.util.Random;
 %0A%0A/**%0A *
@@ -2250,11 +2250,1440 @@
 rue;%0A  %7D
+%0A%0A  public static Id newArcId() %7B%0A    return Id.fromString(Id.generateId());%0A  %7D%0A%0A  // copied from /modalities/dom/components/generate-id.js%0A  private static long lastPushTime = -1;%0A  private static int lastRandChars%5B%5D = new int%5B12%5D;%0A  private static final String pushChars =%0A      %22-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz%22;%0A%0A  private static String generateId() %7B%0A    // TODO: Inject a Clock (so that tests can be repeatable and deterministic).%0A    long now = new Date().getTime();%0A    boolean duplicateTime = now == lastPushTime;%0A    lastPushTime = now;%0A%0A    char timeStampChars%5B%5D = new char%5B8%5D;%0A    for (int i = 7; i %3E= 0; i--) %7B%0A      timeStampChars%5Bi%5D = pushChars.charAt((int) (now %25 64));%0A      now = (int) Math.floor(now / 64);%0A    %7D%0A    if (now != 0) throw new AssertionError(%22We should have converted the entire timestamp.%22);%0A%0A    String id = String.valueOf(timeStampChars);%0A%0A    if (!duplicateTime) %7B%0A      for (int i = 0; i %3C 12; i++) %7B%0A        lastRandChars%5Bi%5D = (int) Math.floor(Math.random() * 64);%0A      %7D%0A    %7D else %7B%0A      int i;%0A      // If the timestamp hasn't changed since last push, use the same random number, except%0A      // incremented by 1.%0A      for (i = 11; i %3E= 0 && lastRandChars%5Bi%5D == 63; i--) %7B%0A        lastRandChars%5Bi%5D = 0;%0A      %7D%0A      lastRandChars%5Bi%5D++;%0A    %7D%0A    for (int i = 0; i %3C 12; i++) %7B%0A      id += pushChars.charAt(lastRandChars%5Bi%5D);%0A    %7D%0A    return id;%0A  %7D
 %0A%7D%0A

add java arc id generator (#3501)