/**
   * Parse a string delimited by comma into a list of object instances depending on
   *
   * <pre>itemParser</pre>
   *
   * .
   *
   * @param str String delimited by comma
   * @param itemParser A function to transform a string to an object.
   * @param <T> Type to be transformed from a string
   * @return List of object instances
   */
  static <T> List<T> parseList(String str, Function<String, T> itemParser) {
    if (!str.contains(",")) { // if just one item
      return ImmutableList.of(itemParser.apply(str));
    }

    final ImmutableList.Builder<T> fields = ImmutableList.builder();
    final Stack<Character> stack = new Stack<>();
    int paramStartIdx = 0;
    for (int i = 0; i < str.length(); i++) {
      char c = str.charAt(i);

      if (c == '<' || c == '[' || c == '(') {
        stack.push(c);
      } else if (c == '>') {
        Assert.assertCondition(stack.pop() == '<', "Bad signature: '%s'", str);
      } else if (c == ']') {
        Assert.assertCondition(stack.pop() == '[', "Bad signature: '%s'", str);
      } else if (c == ')') {
        Assert.assertCondition(stack.pop() == '(', "Bad signature: '%s'", str);
      } else if (c == ',') {
        if (stack.isEmpty()) { // ensure outermost type parameters
          fields.add(itemParser.apply(str.substring(paramStartIdx, i)));
          paramStartIdx = i + 1;
        }
      }
    }

    Assert.assertCondition(stack.empty(), "Bad signature: '%s'", str);
    if (paramStartIdx < str.length()) {
      fields.add(itemParser.apply(str.substring(paramStartIdx, str.length())));
    }

    return fields.build();
  }
  /**
   * Make a field from a string representation
   *
   * @param str String
   * @return Field
   */
  static Field parseField(String str) {
    // A field consists of an identifier and a type, and they are delimited by space.
    if (!str.contains(" ")) {
      Assert.assertCondition(false, "Bad field signature: '%s'", str);
    }

    // Stack to track the nested bracket depth
    Stack<Character> stack = new Stack<>();
    int paramStartIdx = 0;
    for (int i = 0; i < str.length(); i++) {
      char c = str.charAt(i);

      if (c == '<' || c == '[' || c == '(') {
        stack.push(c);
      } else if (c == '>') { // for validation
        Assert.assertCondition(stack.pop() == '<', "Bad field signature: '%s'", str);
      } else if (c == ']') { // for validation
        Assert.assertCondition(stack.pop() == '[', "Bad field signature: '%s'", str);
      } else if (c == ')') { // for validation
        Assert.assertCondition(stack.pop() == '(', "Bad field signature: '%s'", str);

      } else if (c == ' ') {
        if (stack.isEmpty()) { // ensure outermost type parameters
          QualifiedIdentifier identifier =
              IdentifierUtil.makeIdentifier(str.substring(paramStartIdx, i), DefaultPolicy());
          String typePart = str.substring(i + 1, str.length());
          return new Field(identifier, decode(typePart));
        }
      }
    }

    return null;
  }
  /**
   * Decode a string representation to a Type.
   *
   * @param signature Type string representation
   * @return Type
   */
  public static Type decode(String signature) {

    // termination condition in this recursion
    if (!(signature.contains("<") || signature.contains("(") || signature.contains("["))) {
      return createType(
          signature,
          ImmutableList.<Type>of(),
          ImmutableList.<Integer>of(),
          ImmutableList.<Field>of());
    }

    final Stack<Character> stack = new Stack<>();
    final Stack<Integer> spanStack = new Stack<>();
    String baseType = null;
    for (int i = 0; i < signature.length(); i++) {
      char c = signature.charAt(i);

      if (c == '<') {
        if (stack.isEmpty()) {
          Assert.assertCondition(baseType == null, "Expected baseName to be null");
          baseType = signature.substring(0, i);
        }
        stack.push('<');
        spanStack.push(i + 1);

      } else if (c == '>') {
        Assert.assertCondition(stack.pop() == '<', "Bad signature: '%s'", signature);
        int paramStartIdx = spanStack.pop();

        if (stack.isEmpty()) { // ensure outermost parameters
          return createType(
              baseType,
              parseList(
                  signature.substring(paramStartIdx, i),
                  new Function<String, Type>() {
                    @Override
                    public Type apply(@Nullable String s) {
                      return decode(s);
                    }
                  }),
              ImmutableList.<Integer>of(),
              ImmutableList.<Field>of());
        }

      } else if (c == '[') {
        if (stack.isEmpty()) {
          Assert.assertCondition(baseType == null, "Expected baseName to be null");
          baseType = signature.substring(0, i);
        }

        stack.push('[');
        spanStack.push(i + 1);

      } else if (c == ']') {
        Assert.assertCondition(stack.pop() == '[', "Bad signature: '%s'", signature);

        int paramStartIdx = spanStack.pop();
        if (stack.isEmpty()) { // ensure outermost parameters
          return createType(
              baseType,
              ImmutableList.<Type>of(),
              ImmutableList.<Integer>of(),
              parseList(
                  signature.substring(paramStartIdx, i),
                  new Function<String, Field>() {
                    @Override
                    public Field apply(@Nullable String s) {
                      return parseField(s);
                    }
                  }));
        }

      } else if (c == '(') {
        if (stack.isEmpty()) {
          Assert.assertCondition(baseType == null, "Expected baseName to be null");
          baseType = signature.substring(0, i);
        }
        stack.push('(');
        spanStack.push(i + 1);

      } else if (c == ')') {
        Assert.assertCondition(stack.pop() == '(', "Bad signature: '%s'", signature);
        int paramStartIdx = spanStack.pop();

        if (stack.isEmpty()) { // ensure outermost parameters
          return createType(
              baseType,
              ImmutableList.<Type>of(),
              parseList(
                  signature.substring(paramStartIdx, i),
                  new Function<String, Integer>() {
                    @Override
                    public Integer apply(@Nullable String s) {
                      return parseValue(s);
                    }
                  }),
              ImmutableList.<Field>of());
        }
      }
    }

    return null;
  }