/*
 * 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.view.View;
import android.view.ViewGroup;

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

import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;

final class ViewGroupDescriptor extends ChainedDescriptor<ViewGroup> {
  private final Map<ViewGroup, ElementContext> mElementToContextMap =
      Collections.synchronizedMap(new IdentityHashMap<ViewGroup, ElementContext>());

  public ViewGroupDescriptor() {
  }

  private ElementContext getOrCreateContext(ViewGroup element) {
    ElementContext context = mElementToContextMap.get(element);
    if (context == null) {
      context = new ElementContext();
      mElementToContextMap.put(element, context);
    }
    return context;
  }

  final void registerDecorView(ViewGroup decorView) {
    ElementContext context = getOrCreateContext(decorView);
    context.markDecorView();
  }

  @Override
  protected void onHook(ViewGroup element) {
    ElementContext context = getOrCreateContext(element);
    context.hook(element);
  }

  @Override
  protected void onUnhook(ViewGroup element) {
    ElementContext context = mElementToContextMap.remove(element);
    context.unhook();
  }

  @Override
  protected void onGetChildren(ViewGroup element, Accumulator<Object> children) {
    ElementContext context = mElementToContextMap.get(element);
    context.getChildren(children);
  }

  private final class ElementContext {
    // 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 IdentityHashMap<View, Object>());

    private ViewGroup mElement;
    private boolean mIsDecorView;

    public void hook(ViewGroup element) {
      mElement = Util.throwIfNull(element);
    }

    public void unhook() {
      if (mElement != null) {
        mElement = null;
        mViewToElementMap.clear();
      }
    }

    public void markDecorView() {
      mIsDecorView = true;
    }

    public void getChildren(Accumulator<Object> children) {
      for (int i = 0, N = mElement.getChildCount(); i < N; ++i) {
        final View child = mElement.getChildAt(i);
        if (isChildVisible(child)) {
          final Object element = getElementForView(child);
          children.store(element);
        }
      }
    }

    private boolean isChildVisible(View child) {
      return !mIsDecorView || !(child instanceof DOMHiddenView);
    }

    private Object getElementForView(View view) {
      if (view == null) {
        return null;
      }

      Object element = mViewToElementMap.get(view);
      if (element != null) {
        return element;
      }

      Object fragment = FragmentCompatUtil.findFragmentForView(view);
      if (fragment != null) {
        mViewToElementMap.put(view, fragment);
        return fragment;
      } else {
        mViewToElementMap.put(view, view);
        return view;
      }
    }
  }
}

@@ -717,16 +717,46 @@
 til.Map;
+%0Aimport java.util.WeakHashMap;
 %0A%0Afinal 
@@ -976,16 +976,530 @@
 t%3E());%0A%0A
+  /**%0A   * This is a cache that maps from a View to the Fragment that contains it. If the View isn't%0A   * contained by a Fragment, then this maps the View to itself. For Views contained by Fragments,%0A   * we emit the Fragment instead, and then let the Fragment's descriptor emit the View as its sole%0A   * child. This allows us to see Fragments in the inspector as part of the UI tree.%0A   */%0A  private final Map%3CView, Object%3E mViewToElementMap =%0A      Collections.synchronizedMap(new WeakHashMap%3CView, Object%3E());%0A%0A
   public
@@ -2257,767 +2257,758 @@
 %0A%0A  
-@Override%0A  protected void onGetChildren(ViewGroup element, Accumulator%3CObject%3E children) %7B%0A    ElementContext context = mElementToContextMap.get(element);%0A    context.getChildren(children);%0A  %7D%0A%0A  private final class ElementContext %7B%0A    // This is a cache that maps from a View to the Fragment that contains it. If the%0A    // View isn't contained by a Fragment, then this maps the View to itself.%0A    // For Views contained by Fragments, we emit the Fragment instead, and then let%0A    // the Fragment's descriptor emit the View as its sole child. This allows us to%0A    // see Fragments in the inspector as part of the UI tree.%0A 
+private Object getElementForView(ViewGroup parentView, View view) %7B%0A    Object element = mViewToElementMap.get(view);%0A    if (element != null) %7B%0A      if (view.getParent() == parentView) %7B%0A        return element;%0A      %7D%0A      mViewToElementMap.remove(view);%0A    %7D%0A%0A    Object fragment = FragmentCompatUtil.findFragmentForView(view);%0A    if (fragment != null) %7B%0A      mViewToElementMap.put(view, fragment);%0A      return fragment;%0A    %7D else %7B%0A      mViewToElementMap.put(view, view);%0A      return view;%0A    %7D%0A  %7D%0A%0A  @Override%0A
   
- 
 pr
-ivate final Map%3CView, Object%3E mViewToElementMap =%0A        Collections.synchronizedMap(new IdentityHashMap%3CView, Object%3E());%0A
+otected void onGetChildren(ViewGroup element, Accumulator%3CObject%3E children) %7B%0A    ElementContext context = mElementToContextMap.get(element);%0A    context.getChildren(children);%0A  %7D%0A%0A  private final class ElementContext %7B
 %0A   
@@ -3250,43 +3250,8 @@
 ll;%0A
-        mViewToElementMap.clear();%0A
     
@@ -3594,16 +3594,26 @@
 ForView(
+mElement, 
 child);%0A
@@ -3795,494 +3795,8 @@
    %7D
-%0A%0A    private Object getElementForView(View view) %7B%0A      if (view == null) %7B%0A        return null;%0A      %7D%0A%0A      Object element = mViewToElementMap.get(view);%0A      if (element != null) %7B%0A        return element;%0A      %7D%0A%0A      Object fragment = FragmentCompatUtil.findFragmentForView(view);%0A      if (fragment != null) %7B%0A        mViewToElementMap.put(view, fragment);%0A        return fragment;%0A      %7D else %7B%0A        mViewToElementMap.put(view, view);%0A        return view;%0A      %7D%0A    %7D
 %0A  %7D

Fix memory leak in ViewGroupDescriptor