/*
 * Copyright (c) 2017-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.litho;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Queue;

import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;

import com.facebook.litho.config.ComponentsConfiguration;
import com.facebook.proguard.annotations.DoNotStrip;

/**
 * Helper class to access metadata from {@link LithoView} that is relevant during end to end
 * tests. In order for the data to be collected, {@link
 * ComponentsConfiguration#isEndToEndTestRun} must be enabled.
 */
@DoNotStrip
public class LithoViewTestHelper {

  /**
   * @see #findTestItems(LithoView, String)
   *
   * <strong>Note:</strong> If there is more than one element mounted under the given key,
   * the last one to render will be returned.
   *
   * @param lithoView The component view the component is mounted to.
   * @param testKey The unique identifier the component was constructed with.
   * @return Test item if found, null otherwise.
   * @throws UnsupportedOperationException If the e2e flag is not enabled in the configuration.
   */
  @DoNotStrip
  @Nullable
  public static TestItem findTestItem(LithoView lithoView, String testKey) {
    final Deque<TestItem> items = lithoView.findTestItems(testKey);

    return items.isEmpty() ? null : items.getLast();
  }

  public static List<LithoView> findLithoViews(View rootView) {
    return findLithoViews(Collections.singleton(rootView));
  }

  public static List<LithoView> findLithoViews(Collection<View> topLevelViews) {
    final Queue<View> queue = new ArrayDeque<>(topLevelViews);
    final List<LithoView> lithoViews = new ArrayList<>();

    View view = queue.poll();
    while (view != null) {
      if (view instanceof ViewGroup) {
        final ViewGroup viewGroup = (ViewGroup) view;
        for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
          final View child = viewGroup.getChildAt(i);
          if (child instanceof ViewGroup) {
            queue.add(child);
          }
          if (child instanceof LithoView) {
            lithoViews.add((LithoView) child);
          }
        }
      }
      view = queue.poll();
    }

    return lithoViews;
  }

  /**
   * Finds a {@link TestItem} given a {@link LithoView} based on the test key it was
   * assigned during construction.
   *
   * <strong>Example use:</strong>
   * <pre>{@code
   *  final LithoView lithoView = ComponentTestHelper.mountComponent(
   *      mContext,
   *      new InlineLayoutSpec() {
   *        @Override
   *        protected ComponentLayout onCreateLayout(ComponentContext c) {
   *          return Column.create(c)
   *              .child(
   *                  Column.create(c)
   *                      .child(TestDrawableComponent.create(c))
   *                      .child(TestDrawableComponent.create(c))
   *                      .testKey("mytestkey"))
   *              .build();
   *        }
   *      });
   *  final TestItem testItem = LithoViewTestHelper.findTestItem(lithoView, "mytestkey");
   *  }
   * </pre>
   *
   * @param lithoView The component view the component is mounted to.
   * @param testKey The unique identifier the component was constructed with.
   * @return Queue of mounted items in order by mount time.
   * @throws UnsupportedOperationException If the e2e flag is not enabled in the configuration.
   */
  @DoNotStrip
  @NonNull
  public static Deque<TestItem> findTestItems(LithoView lithoView, String testKey) {
    return lithoView.findTestItems(testKey);
  }

  /**
   * Provide a nested string representation of a LithoView and its nested
   * components for debugging purposes.
   *
   * @param view A Litho view with mounted components.
   */
  @DoNotStrip
  public static String viewToString(LithoView view) {
    return viewToString(DebugComponent.getRootInstance(view), 0);
  }

  private static String viewToString(@Nullable DebugComponent debugComponent, int depth) {
    if (debugComponent == null) {
      return "";
    }

    final StringBuilder sb = new StringBuilder();

    if (depth > 0) {
      sb.append('\n');
    }

    for (int i = 0; i < depth; i++) {
      sb.append("  ");
    }

    sb.append(debugComponent.getSimpleName());

    final Rect bounds = debugComponent.getBounds();
    sb.append('{');

    sb.append(bounds.left);
    sb.append(", ");
    sb.append(bounds.top);
    sb.append(" - ");
    sb.append(bounds.right);
    sb.append(", ");
    sb.append(bounds.bottom);

    final String testKey = debugComponent.getTestKey();
    if (!TextUtils.isEmpty(testKey)) {
      sb.append(String.format(" testKey=\"%s\"", testKey));
    }

    final String textContent = debugComponent.getTextContent();
    if (!TextUtils.isEmpty(textContent)) {
      sb.append(String.format(" text=\"%s\"", textContent));
    }

    if (debugComponent.isClickable()) {
      sb.append(" [clickable]");
    }

    sb.append('}');

    for (DebugComponent child : debugComponent.getChildComponents()) {
      sb.append(viewToString(child, depth + 1));
    }

    return sb.toString();
  }
}

@@ -352,177 +352,14 @@
 til.
-Array
 Deque;
-%0Aimport java.util.ArrayList;%0Aimport java.util.Collection;%0Aimport java.util.Collections;%0Aimport java.util.Deque;%0Aimport java.util.List;%0Aimport java.util.Queue;
 %0A%0Aim
@@ -507,65 +507,8 @@
 ils;
-%0Aimport android.view.View;%0Aimport android.view.ViewGroup;
 %0A%0Aim
@@ -1549,24 +1549,24 @@
 (testKey);%0A%0A
+
     return i
@@ -1615,880 +1615,8 @@
  %7D%0A%0A
-  public static List%3CLithoView%3E findLithoViews(View rootView) %7B%0A    return findLithoViews(Collections.singleton(rootView));%0A  %7D%0A%0A  public static List%3CLithoView%3E findLithoViews(Collection%3CView%3E topLevelViews) %7B%0A    final Queue%3CView%3E queue = new ArrayDeque%3C%3E(topLevelViews);%0A    final List%3CLithoView%3E lithoViews = new ArrayList%3C%3E();%0A%0A    View view = queue.poll();%0A    while (view != null) %7B%0A      if (view instanceof ViewGroup) %7B%0A        final ViewGroup viewGroup = (ViewGroup) view;%0A        for (int i = viewGroup.getChildCount() - 1; i %3E= 0; i--) %7B%0A          final View child = viewGroup.getChildAt(i);%0A          if (child instanceof ViewGroup) %7B%0A            queue.add(child);%0A          %7D%0A          if (child instanceof LithoView) %7B%0A            lithoViews.add((LithoView) child);%0A          %7D%0A        %7D%0A      %7D%0A      view = queue.poll();%0A    %7D%0A%0A    return lithoViews;%0A  %7D%0A%0A
   /*

Revert D5442625: [litho] Update LithoSelendroidDriverDelegate to use List instead of Set for determinism