class NodesSpecifier {

  public static List<AutocompleteSuggestion> autoComplete(
      String query, Set<String> nodes, NodeRolesData nodeRoleData) {
    final String finalQuery = query == null ? "" : query;

    // fill in types; will produce something only with finalQuery is empty or something like "NA"
    List<AutocompleteSuggestion> suggestions =
        Arrays.stream(Type.values())
            .filter(type -> type.toString().startsWith(finalQuery.toUpperCase()))
            .map(
                type ->
                    new AutocompleteSuggestion(
                        type.toString() + ":", true, "Select nodes by " + type.toString()))
            .collect(Collectors.toList());

    // for reading code below, remember splitting "PRE:" or "PRE" yields ["PRE"]
    String[] parts = finalQuery.split(":");

    if (!finalQuery.contains(":") || finalQuery.toUpperCase().startsWith("NAME:")) { // NAME query
      String namePrefix =
          finalQuery.equalsIgnoreCase("NAME:") ? "" : (parts.length == 1 ? finalQuery : parts[1]);
      List<AutocompleteSuggestion> nameSuggestions =
          nodes.stream()
              .filter(n -> n.startsWith(namePrefix))
              .map(n -> new AutocompleteSuggestion(n, false))
              .collect(Collectors.toList());
      if (!nameSuggestions.isEmpty()) {
        suggestions.add(
            new AutocompleteSuggestion(
                finalQuery + ".*", false, "All nodes whose names match " + namePrefix + ".*"));
        suggestions.addAll(nameSuggestions);
      }
    }

    if (finalQuery.toUpperCase().startsWith("ROLE:") && nodeRoleData != null) { // ROLE query
      if (parts.length == 3 || (parts.length == 2 && finalQuery.endsWith(":"))) {
        String roleDimension = parts[1];
        Optional<NodeRoleDimension> optDimension =
            Optional.ofNullable(nodeRoleData.toNodeRoleDimensions().get(roleDimension));
        if (optDimension.isPresent()) {
          NodeRoleDimension dimension = optDimension.get();
          String roleDimensionPrefix = "ROLE:" + dimension.getName() + ":";
          String rolePrefix = finalQuery.endsWith(":") ? "" : parts[2];
          List<AutocompleteSuggestion> roleSuggestions =
              dimension.roleNamesFor(nodes).stream()
                  .filter(role -> role.startsWith(rolePrefix))
                  .map(
                      role ->
                          new AutocompleteSuggestion(
                              "ROLE:" + dimension.getName() + ":" + role,
                              false,
                              "All nodes that belong to this role"))
                  .collect(Collectors.toList());
          if (!roleSuggestions.isEmpty()) {
            suggestions.add(
                new AutocompleteSuggestion(
                    roleDimensionPrefix + rolePrefix + ".*",
                    false,
                    "All nodes with matching roles"));
            suggestions.addAll(roleSuggestions);
          }
        }
      } else {
        String roleDimPrefix = finalQuery.equalsIgnoreCase("ROLE:") ? "" : parts[1];
        suggestions.addAll(
            nodeRoleData.toNodeRoleDimensions().values().stream()
                .filter(dim -> dim.getName().startsWith(roleDimPrefix))
                .map(
                    dim ->
                        new AutocompleteSuggestion(
                            "ROLE:" + dim.getName() + ":",
                            true,
                            "Select nodes using this dimension"))
                .collect(ImmutableSet.toImmutableSet()));
      }
    }

    int rank = 0;
    for (AutocompleteSuggestion suggestion : suggestions) {
      AutocompleteSuggestion.builder(suggestion).setRank(rank).build();
      rank++;
    }

    return suggestions;
  }

  public  NodesSpecifier(String expression, boolean caseSensitive);
  @JsonCreator public  NodesSpecifier(String expression);

}