/*
 * Copyright (c) 2014-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.stetho.inspector.elements.android;

import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;

import com.facebook.stetho.common.Accumulator;
import com.facebook.stetho.common.android.FragmentCompatUtil;
import com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;

import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;

import javax.annotation.Nullable;

final class ViewGroupDescriptor extends AbstractChainedDescriptor<ViewGroup>
    implements HighlightableDescriptor<ViewGroup> {

  /**
   * This is a cache that maps from a View to the Fragment that contains it. If the View isn't
   * contained by a Fragment, then this maps the View to itself. For Views contained by Fragments,
   * we emit the Fragment instead, and then let the Fragment's descriptor emit the View as its sole
   * child. This allows us to see Fragments in the inspector as part of the UI tree.
   */
  private final Map<View, Object> mViewToElementMap =
      Collections.synchronizedMap(new WeakHashMap<View, Object>());

  public ViewGroupDescriptor() {
  }

  @Override
  protected void onGetChildren(ViewGroup element, Accumulator<Object> children) {
    for (int i = 0, N = element.getChildCount(); i < N; ++i) {
      final View childView = element.getChildAt(i);
      if (isChildVisible(childView)) {
        final Object childElement = getElementForView(element, childView);
        children.store(childElement);
      }
    }
  }

  private boolean isChildVisible(View child) {
    return !(child instanceof DocumentHiddenView);
  }

  private Object getElementForView(ViewGroup parentView, View childView) {
    Object value = mViewToElementMap.get(childView);
    if (value != null) {
      Object element = getElement(childView, value);

      // The parent of a View may have changed since we stashed it into the cache.
      // If that's the case then we can't use the cache's answer.
      if (element != null && childView.getParent() == parentView) {
        return element;
      }
      mViewToElementMap.remove(childView);
    }

    /**
     * Note that we do NOT emit DialogFragments. Those get emitted via ActivityDescriptor.
     * We do the check here so that we can also cache the cost of calling
     * {@link FragmentCompatUtil#isDialogFragment(Object)}.
     */

    Object fragment = FragmentCompatUtil.findFragmentForView(childView);
    if (fragment != null && !FragmentCompatUtil.isDialogFragment(fragment)) {
      mViewToElementMap.put(childView, new WeakReference<>(fragment));
      return fragment;
    } else {
      // No need to store a strong reference to the childView in the value. We'll just store this
      // object and when pull the value out of the map we'll check for this object and just use the
      // key instead.
      mViewToElementMap.put(childView, this);
      return childView;
    }
  }

  @SuppressWarnings("unchecked")
  private Object getElement(View childView, Object value) {
    if (value == this) {
      return childView;
    } else {
      return ((WeakReference<Object>) value).get();
    }
  }

  @Override
  @Nullable
  public View getViewAndBoundsForHighlighting(ViewGroup element, Rect bounds) {
    return element;
  }

  @Nullable
  @Override
  public Object getElementToHighlightAtPosition(ViewGroup element, int x, int y, Rect bounds) {
    View hitChild = null;
    for (int i = element.getChildCount() - 1; i >= 0; --i) {
      final View childView = element.getChildAt(i);
      final boolean hasChildren = childView instanceof ViewGroup &&
          ((ViewGroup) childView).getChildCount() > 0;
      if (isChildVisible(childView) &&
          childView.getVisibility() == View.VISIBLE &&
          (hasChildren || childView.isFocusable())) {
        childView.getHitRect(bounds);
        if (bounds.contains(x, y)) {
          hitChild = childView;
          break;
        }
      }
    }

    if (hitChild != null) {
      return hitChild;
    } else {
      bounds.set(0, 0, element.getWidth(), element.getHeight());
      return element;
    }
  }
}

@@ -3866,131 +3866,8 @@
 i);%0A
-      final boolean hasChildren = childView instanceof ViewGroup &&%0A          ((ViewGroup) childView).getChildCount() %3E 0;%0A
     
@@ -3956,62 +3956,8 @@
 IBLE
- &&%0A          (hasChildren %7C%7C childView.isFocusable())
 ) %7B%0A

Allow hitting empty viewgroups