/**
   * Computes a vertical "gap" match - a preferred distance from the nearest edge, including margin
   * edges
   */
  private void addRowGapMatch(Rect bounds, int y1, int y2, List<GridMatch> rowMatches, int max) {
    if (y1 < bounds.y + MARGIN_SIZE + max) {
      int matchedLine = bounds.y + MARGIN_SIZE;
      int distance = abs(matchedLine - y1);
      if (distance <= max) {
        boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine;
        rowMatches.add(
            new GridMatch(SegmentType.TOP, distance, matchedLine, 0, createCell, MARGIN_SIZE));
      }
    } else if (y2 > bounds.y2() - MARGIN_SIZE - max) {
      int matchedLine = bounds.y2() - MARGIN_SIZE;
      int distance = abs(matchedLine - y2);
      if (distance <= max) {
        // This does not yet work properly; we need to use columnWeights to achieve this
        // boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine;
        // rowMatches.add(new GridMatch(SegmentType.BOTTOM, distance, matchedLine,
        //        mGrid.actualRowCount - 1, createCell, MARGIN_SIZE));
      }
    } else {
      int rowBottom = mGrid.getRow(y1 - SHORT_GAP_DP);
      int rowY = mGrid.getRowMaxY(rowBottom);
      int matchedLine = rowY + SHORT_GAP_DP;
      int distance = abs(matchedLine - y1);
      if (distance <= max) {
        boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine;
        rowMatches.add(
            new GridMatch(
                SegmentType.TOP, distance, matchedLine, rowBottom, createCell, SHORT_GAP_DP));
      }

      // Add a row directly adjacent (no gap)
      rowBottom = mGrid.getRow(y1);
      rowY = mGrid.getRowMaxY(rowBottom);
      matchedLine = rowY;
      distance = abs(matchedLine - y1);
      distance += 2; // See explanation in addColumnGapMatch
      if (distance <= max) {
        boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine;
        rowMatches.add(
            new GridMatch(SegmentType.TOP, distance, matchedLine, rowBottom, createCell, 0));
      }
    }
  }
 /** Adds a match to align the bottom edge with some other edge. */
 private void addBottomMatch(int y2, List<GridMatch> rowMatches, int max) {
   int rowBottom = (mGrid.getViewCount() == 0) ? 0 : mGrid.getClosestRow(y2);
   int distance = mGrid.getRowDistance(rowBottom, y2);
   if (distance < max) {
     int rowY = mGrid.getRowY(rowBottom);
     if (rowY > mGrid.layout.getBounds().y) {
       rowMatches.add(
           new GridMatch(SegmentType.BOTTOM, distance, rowY, rowBottom, false, UNDEFINED));
     }
   }
 }
  /**
   * Computes the best horizontal and vertical matches for a drag to the given position.
   *
   * @param feedback a {@link DropFeedback} object containing drag state like the drag bounds and
   *     the drag baseline
   * @param p the mouse position
   */
  public void computeMatches(DropFeedback feedback, Point p) {
    mRowMatch = mColumnMatch = null;
    feedback.tooltip = null;

    Rect bounds = mGrid.layout.getBounds();
    int x1 = p.x;
    int y1 = p.y;

    Rect dragBounds = feedback.dragBounds;
    int w = dragBounds != null ? dragBounds.w : 0;
    int h = dragBounds != null ? dragBounds.h : 0;
    if (!GridLayoutRule.sGridMode) {
      if (dragBounds != null) {
        // Sometimes the items are centered under the mouse so
        // offset by the top left corner distance
        x1 += dragBounds.x;
        y1 += dragBounds.y;
      }

      int x2 = x1 + w;
      int y2 = y1 + h;

      if (x2 < bounds.x || y2 < bounds.y || x1 > bounds.x2() || y1 > bounds.y2()) {
        return;
      }

      List<GridMatch> columnMatches = new ArrayList<GridMatch>();
      List<GridMatch> rowMatches = new ArrayList<GridMatch>();
      int max = BaseLayoutRule.getMaxMatchDistance();

      // Column matches:
      addLeftSideMatch(x1, columnMatches, max);
      addRightSideMatch(x2, columnMatches, max);
      addCenterColumnMatch(bounds, x1, y1, x2, y2, columnMatches, max);

      // Row matches:
      int row = (mGrid.getViewCount() == 0) ? 0 : mGrid.getClosestRow(y1);
      int rowY = mGrid.getRowY(row);
      addTopMatch(y1, rowMatches, max, row, rowY);
      addBaselineMatch(feedback.dragBaseline, y1, rowMatches, max, row, rowY);
      addBottomMatch(y2, rowMatches, max);

      // Look for gap-matches: Predefined spacing between widgets.
      // TODO: Make this use metadata for predefined spacing between
      // pairs of types of components. For example, buttons have certain
      // inserts in their 9-patch files (depending on the theme) that should
      // be considered and subtracted from the overall proposed distance!
      addColumnGapMatch(bounds, x1, x2, columnMatches, max);
      addRowGapMatch(bounds, y1, y2, rowMatches, max);

      // Fallback: Split existing cell. Also do snap-to-grid.
      if (GridLayoutRule.sSnapToGrid) {
        x1 = ((x1 - MARGIN_SIZE - bounds.x) / GRID_SIZE) * GRID_SIZE + MARGIN_SIZE + bounds.x;
        y1 = ((y1 - MARGIN_SIZE - bounds.y) / GRID_SIZE) * GRID_SIZE + MARGIN_SIZE + bounds.y;
        x2 = x1 + w;
        y2 = y1 + h;
      }

      if (columnMatches.size() == 0 && x1 >= bounds.x) {
        // Split the current cell since we have no matches
        // TODO: Decide whether it should be gravity left or right...
        columnMatches.add(
            new GridMatch(
                SegmentType.LEFT, 0, x1, mGrid.getColumn(x1), true /* createCell */, UNDEFINED));
      }
      if (rowMatches.size() == 0 && y1 >= bounds.y) {
        rowMatches.add(
            new GridMatch(
                SegmentType.TOP, 0, y1, mGrid.getRow(y1), true /* createCell */, UNDEFINED));
      }

      // Pick best matches
      Collections.sort(rowMatches);
      Collections.sort(columnMatches);

      mColumnMatch = null;
      mRowMatch = null;
      String columnDescription = null;
      String rowDescription = null;
      if (columnMatches.size() > 0) {
        mColumnMatch = columnMatches.get(0);
        columnDescription = mColumnMatch.getDisplayName(mGrid.layout);
      }
      if (rowMatches.size() > 0) {
        mRowMatch = rowMatches.get(0);
        rowDescription = mRowMatch.getDisplayName(mGrid.layout);
      }

      if (columnDescription != null && rowDescription != null) {
        feedback.tooltip = columnDescription + '\n' + rowDescription;
      }

      feedback.invalidTarget = mColumnMatch == null || mRowMatch == null;
    } else {
      // Find which cell we're inside.

      // TODO: Find out where within the cell we are, and offer to tweak the gravity
      // based on the position.
      int column = mGrid.getColumn(x1);
      int row = mGrid.getRow(y1);

      int leftDistance = mGrid.getColumnDistance(column, x1);
      int rightDistance = mGrid.getColumnDistance(column + 1, x1);
      int topDistance = mGrid.getRowDistance(row, y1);
      int bottomDistance = mGrid.getRowDistance(row + 1, y1);

      int SLOP = 2;
      int radius = mRule.getNewCellSize();
      if (rightDistance < radius + SLOP) {
        column = Math.min(column + 1, mGrid.actualColumnCount);
        leftDistance = rightDistance;
      }
      if (bottomDistance < radius + SLOP) {
        row = Math.min(row + 1, mGrid.actualRowCount);
        topDistance = bottomDistance;
      }

      boolean createColumn = leftDistance < radius + SLOP;
      boolean createRow = topDistance < radius + SLOP;
      if (x1 >= bounds.x2()) {
        createColumn = true;
      }
      if (y1 >= bounds.y2()) {
        createRow = true;
      }

      int cellWidth = leftDistance + rightDistance;
      int cellHeight = topDistance + bottomDistance;
      SegmentType horizontalType = SegmentType.LEFT;
      SegmentType verticalType = SegmentType.TOP;
      int minDistance = 10; // Don't center or right/bottom align in tiny cells
      if (!createColumn
          && leftDistance > minDistance
          && dragBounds != null
          && dragBounds.w < cellWidth - 10) {
        if (rightDistance < leftDistance) {
          horizontalType = SegmentType.RIGHT;
        }

        int centerDistance = Math.abs(cellWidth / 2 - leftDistance);
        if (centerDistance < leftDistance / 2 && centerDistance < rightDistance / 2) {
          horizontalType = SegmentType.CENTER_HORIZONTAL;
        }
      }
      if (!createRow
          && topDistance > minDistance
          && dragBounds != null
          && dragBounds.h < cellHeight - 10) {
        if (bottomDistance < topDistance) {
          verticalType = SegmentType.BOTTOM;
        }
        int centerDistance = Math.abs(cellHeight / 2 - topDistance);
        if (centerDistance < topDistance / 2 && centerDistance < bottomDistance / 2) {
          verticalType = SegmentType.CENTER_VERTICAL;
        }
      }

      mColumnMatch = new GridMatch(horizontalType, 0, x1, column, createColumn, 0);
      mRowMatch = new GridMatch(verticalType, 0, y1, row, createRow, 0);

      StringBuilder description = new StringBuilder(50);
      String rowString = Integer.toString(mColumnMatch.cellIndex + 1);
      String columnString = Integer.toString(mRowMatch.cellIndex + 1);
      if (mRowMatch.createCell && mRowMatch.cellIndex < mGrid.actualRowCount) {
        description.append(String.format("Shift row %1$d down", mRowMatch.cellIndex + 1));
        description.append('\n');
      }
      if (mColumnMatch.createCell && mColumnMatch.cellIndex < mGrid.actualColumnCount) {
        description.append(String.format("Shift column %1$d right", mColumnMatch.cellIndex + 1));
        description.append('\n');
      }
      description.append(String.format("Insert into cell (%1$s,%2$s)", rowString, columnString));
      description.append('\n');
      description.append(
          String.format(
              "Align %1$s, %2$s",
              horizontalType.name().toLowerCase(Locale.US),
              verticalType.name().toLowerCase(Locale.US)));
      feedback.tooltip = description.toString();
    }
  }