/**
   * Used by the {@link #translateCompoundSelector(UiSelector, AccessibilityNodeInfo, boolean)} to
   * translate the pattern_selector portion. It has the following format:
   *
   * <p>pattern_selector = ... PATTERN=By[instance=x PATTERN=[regular_selector]]<br>
   *
   * <p>pattern_selectors requires search to be performed as regular_selector but where
   * regular_selector search returns immediately upon a successful match, the search for
   * pattern_selector continues until the requested matched instance of that pattern is encountered.
   *
   * <p>Counting UI objects requires using pattern_selectors. The counting search is the same as a
   * pattern_search however we're not looking to match an instance of the pattern but rather
   * continuously walking the accessibility node hierarchy while counting patterns until the end of
   * the tree.
   *
   * @param subSelector
   * @param fromNode
   * @param isCounting
   * @return null of node is not found or if counting mode is true. See {@link
   *     #translateCompoundSelector(UiSelector, AccessibilityNodeInfo, boolean)}
   */
  private AccessibilityNodeInfo translatePatternSelector(
      UiSelector subSelector, AccessibilityNodeInfo fromNode, boolean isCounting) {

    if (subSelector.hasPatternSelector()) {
      // Since pattern_selectors are also the type of selectors used when counting,
      // we check if this is a counting run or an indexing run
      if (isCounting)
        // since we're counting, we reset the indexer so to terminates the search when
        // the end of tree is reached. The count will be in mPatternCount
        mPatternIndexer = -1;
      else
        // terminates the search once we match the pattern's instance
        mPatternIndexer = subSelector.getInstance();

      // A pattern is wrapped in a PATTERN[instance=x PATTERN[the_pattern]]
      subSelector = subSelector.getPatternSelector();
      if (subSelector == null) {
        Log.e(LOG_TAG, "Pattern portion of the selector is null or not defined");
        return null; // there is an implementation fault
      }
      // save the current indent level as parent indent before pattern searches
      // begin under the current tree position.
      mLogParentIndent = ++mLogIndent;
      return findNodePatternRecursive(subSelector, fromNode, 0, subSelector);
    }

    Log.e(LOG_TAG, "Selector must have a pattern selector defined"); // implementation fault?
    return null;
  }
  private AccessibilityNodeInfo findNodeRegularRecursive(
      UiSelector subSelector, AccessibilityNodeInfo fromNode, int index) {

    if (subSelector.isMatchFor(fromNode, index)) {
      if (DEBUG) {
        Log.d(LOG_TAG, formatLog(String.format("%s", subSelector.dumpToString(false))));
      }
      if (subSelector.isLeaf()) {
        return fromNode;
      }
      if (subSelector.hasChildSelector()) {
        mLogIndent++; // next selector
        subSelector = subSelector.getChildSelector();
        if (subSelector == null) {
          Log.e(LOG_TAG, "Error: A child selector without content");
          return null; // there is an implementation fault
        }
      } else if (subSelector.hasParentSelector()) {
        mLogIndent++; // next selector
        subSelector = subSelector.getParentSelector();
        if (subSelector == null) {
          Log.e(LOG_TAG, "Error: A parent selector without content");
          return null; // there is an implementation fault
        }
        // the selector requested we start at this level from
        // the parent node from the one we just matched
        fromNode = fromNode.getParent();
        if (fromNode == null) return null;
      }
    }

    int childCount = fromNode.getChildCount();
    boolean hasNullChild = false;
    for (int i = 0; i < childCount; i++) {
      AccessibilityNodeInfo childNode = fromNode.getChild(i);
      if (childNode == null) {
        Log.w(
            LOG_TAG,
            String.format("AccessibilityNodeInfo returned a null child (%d of %d)", i, childCount));
        if (!hasNullChild) {
          Log.w(LOG_TAG, String.format("parent = %s", fromNode.toString()));
        }
        hasNullChild = true;
        continue;
      }
      if (!childNode.isVisibleToUser()) {
        if (VERBOSE)
          Log.v(LOG_TAG, String.format("Skipping invisible child: %s", childNode.toString()));
        continue;
      }
      AccessibilityNodeInfo retNode = findNodeRegularRecursive(subSelector, childNode, i);
      if (retNode != null) {
        return retNode;
      }
    }
    return null;
  }
  private AccessibilityNodeInfo findNodePatternRecursive(
      UiSelector subSelector,
      AccessibilityNodeInfo fromNode,
      int index,
      UiSelector originalPattern) {

    if (subSelector.isMatchFor(fromNode, index)) {
      if (subSelector.isLeaf()) {
        if (mPatternIndexer == 0) {
          if (DEBUG)
            Log.d(LOG_TAG, formatLog(String.format("%s", subSelector.dumpToString(false))));
          return fromNode;
        } else {
          if (DEBUG)
            Log.d(LOG_TAG, formatLog(String.format("%s", subSelector.dumpToString(false))));
          mPatternCounter++; // count the pattern matched
          mPatternIndexer--; // decrement until zero for the instance requested

          // At a leaf selector within a group and still not instance matched
          // then reset the  selector to continue search from current position
          // in the accessibility tree for the next pattern match up until the
          // pattern index hits 0.
          subSelector = originalPattern;
          // starting over with next pattern search so reset to parent level
          mLogIndent = mLogParentIndent;
        }
      } else {
        if (DEBUG) Log.d(LOG_TAG, formatLog(String.format("%s", subSelector.dumpToString(false))));

        if (subSelector.hasChildSelector()) {
          mLogIndent++; // next selector
          subSelector = subSelector.getChildSelector();
          if (subSelector == null) {
            Log.e(LOG_TAG, "Error: A child selector without content");
            return null;
          }
        } else if (subSelector.hasParentSelector()) {
          mLogIndent++; // next selector
          subSelector = subSelector.getParentSelector();
          if (subSelector == null) {
            Log.e(LOG_TAG, "Error: A parent selector without content");
            return null;
          }
          fromNode = fromNode.getParent();
          if (fromNode == null) return null;
        }
      }
    }

    int childCount = fromNode.getChildCount();
    boolean hasNullChild = false;
    for (int i = 0; i < childCount; i++) {
      AccessibilityNodeInfo childNode = fromNode.getChild(i);
      if (childNode == null) {
        Log.w(
            LOG_TAG,
            String.format("AccessibilityNodeInfo returned a null child (%d of %d)", i, childCount));
        if (!hasNullChild) {
          Log.w(LOG_TAG, String.format("parent = %s", fromNode.toString()));
        }
        hasNullChild = true;
        continue;
      }
      if (!childNode.isVisibleToUser()) {
        if (DEBUG)
          Log.d(LOG_TAG, String.format("Skipping invisible child: %s", childNode.toString()));
        continue;
      }
      AccessibilityNodeInfo retNode =
          findNodePatternRecursive(subSelector, childNode, i, originalPattern);
      if (retNode != null) {
        return retNode;
      }
    }
    return null;
  }
  /**
   * A compoundSelector encapsulate both Regular and Pattern selectors. The formats follows:
   *
   * <p>regular_selector = By[attributes... CHILD=By[attributes... CHILD=By[....]]] <br>
   * pattern_selector = ...CONTAINER=By[..] PATTERN=By[instance=x PATTERN=[regular_selector] <br>
   * compound_selector = [regular_selector [pattern_selector]]
   *
   * <p>regular_selectors are the most common form of selectors and the search for them is
   * straightforward. On the other hand pattern_selectors requires search to be performed as in
   * regular_selector but where regular_selector search returns immediately upon a successful match,
   * the search for pattern_selector continues until the requested matched _instance_ of that
   * pattern is matched.
   *
   * <p>Counting UI objects requires using pattern_selectors. The counting search is the same as a
   * pattern_search however we're not looking to match an instance of the pattern but rather
   * continuously walking the accessibility node hierarchy while counting matched patterns, until
   * the end of the tree.
   *
   * <p>If both present, order of parsing begins with CONTAINER followed by PATTERN then the top
   * most selector is processed as regular_selector within the context of the previous CONTAINER and
   * its PATTERN information. If neither is present then the top selector is directly treated as
   * regular_selector. So the presence of a CONTAINER and PATTERN within a selector simply dictates
   * that the selector matching will be constraint to the sub tree node where the CONTAINER and its
   * child PATTERN have identified.
   *
   * @param selector
   * @param fromNode
   * @param isCounting
   * @return AccessibilityNodeInfo
   */
  private AccessibilityNodeInfo translateCompoundSelector(
      UiSelector selector, AccessibilityNodeInfo fromNode, boolean isCounting) {

    // Start translating compound selectors by translating the regular_selector first
    // The regular_selector is then used as a container for any optional pattern_selectors
    // that may or may not be specified.
    if (selector.hasContainerSelector())
      // nested pattern selectors
      if (selector.getContainerSelector().hasContainerSelector()) {
        fromNode = translateCompoundSelector(selector.getContainerSelector(), fromNode, false);
        initializeNewSearch();
      } else fromNode = translateReqularSelector(selector.getContainerSelector(), fromNode);
    else fromNode = translateReqularSelector(selector, fromNode);

    if (fromNode == null) {
      if (DEBUG) Log.d(LOG_TAG, "Container selector not found: " + selector.dumpToString(false));
      return null;
    }

    if (selector.hasPatternSelector()) {
      fromNode = translatePatternSelector(selector.getPatternSelector(), fromNode, isCounting);

      if (isCounting) {
        Log.i(LOG_TAG, String.format("Counted %d instances of: %s", mPatternCounter, selector));
        return null;
      } else {
        if (fromNode == null) {
          if (DEBUG) Log.d(LOG_TAG, "Pattern selector not found: " + selector.dumpToString(false));
          return null;
        }
      }
    }

    // translate any additions to the selector that may have been added by tests
    // with getChild(By selector) after a container and pattern selectors
    if (selector.hasContainerSelector() || selector.hasPatternSelector()) {
      if (selector.hasChildSelector() || selector.hasParentSelector())
        fromNode = translateReqularSelector(selector, fromNode);
    }

    if (fromNode == null) {
      if (DEBUG) Log.d(LOG_TAG, "Object Not Found for selector " + selector);
      return null;
    }
    Log.i(LOG_TAG, String.format("Matched selector: %s <<==>> [%s]", selector, fromNode));
    return fromNode;
  }