// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.test.pagecontroller.utils;

import android.content.res.Resources;

import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.uiautomator.By;

import java.util.regex.Pattern;

/**
 * Locators that find UIObject2 nodes.
 */
public final class Ui2Locators {
    private static Resources sMockResources;

    @VisibleForTesting
    static void setResources(Resources mockResources) {
        sMockResources = mockResources;
    }

    /**
     * Returns a locator that find node(s) matching any one of R.id.* entry names (ignoring the
     * package name).
     *
     * This is the preferred way to locate nodes if the R.id.* is available.
     *
     * @param id             The layout resource id of the corresponding view node.
     * @param additionalIds  Optional additional layout resource ids to match.
     * @return               A locator that will match against the any of the ids.
     */
    public static IUi2Locator withAnyResEntry(@IdRes int id, @IdRes int... additionalIds) {
        return withAnyResId(getResourceEntryName(id), getResourceEntryNames(additionalIds));
    }

    /**
     * Locates the node(s) having a resource id name among a list of names (excluding the
     * package:id/ part).
     *
     * @param firstId       The first id to match against.
     * @param additionalIds Optional ids to match against.
     * @return              A locator that will find node(s) if they match any of the ids.
     */
    public static IUi2Locator withAnyResId(String firstId, String... additionalIds) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("(");
        stringBuilder.append(firstId);
        for (int i = 0; i < additionalIds.length; i++) {
            stringBuilder.append("|" + additionalIds[i]);
        }
        stringBuilder.append(")");
        return withResIdRegex(stringBuilder.toString());
    }

    /**
     * Locates the node(s) having a resource id name that matches a regex (excluding the package:id/
     * part).  Prefer using {@link #withAnyResIdEntry(int, int...)}
     *
     * @param idRegex Regular expression to match ids against.
     * @return        A locator that will find node(s) whose ids match the given regular
     *                expression.
     */
    public static IUi2Locator withResIdRegex(@NonNull String idRegex) {
        return withResNameRegex("^.*:id/" + idRegex + "$");
    }

    /**
     * Locates the node(s) having an exact resource name (including the package:id/ part).
     *
     * @param resourceName The resource name to find.
     * @return             A locator that will find node(s) whose resource name matches.
     */
    public static IUi2Locator withResName(@NonNull String resourceName) {
        return new BySelectorUi2Locator(By.res(resourceName));
    }

    /**
     * Locates the node(s) having a resource name that match the regex (including the package:id/
     * part).
     *
     * @param resourceNameRegex The resource name to find.
     * @return                  A locator that will find node(s) whose resource name matches.
     */
    public static IUi2Locator withResNameRegex(@NonNull String resourceNameRegex) {
        return new BySelectorUi2Locator(By.res(Pattern.compile(resourceNameRegex)));
    }

    /**
     * Locates the node(s) having a text string from resource id.
     *
     * This is the preferred way to locate nodes by text content if the string
     * resource id is available.
     *
     * @param idString  The id of the string.
     * @return          A locator that will find the node(s) with text that matches the
     *                  string identified by the given id.
     */
    public static IUi2Locator withTextString(int idString) {
        return new BySelectorUi2Locator(By.text(getResourceStringName(idString)));
    }

    /**
     * Locates the node(s) by position in the siblings list, recursively if more indices are
     * specified.
     *
     * The order of the nodes is determined by the implementation of UiObject2.findObjects,
     * but not documented, at present it's pre-order.
     *
     * @param childIndex        The index of the child.
     * @param descendantIndices Optional additional indices of descendants.
     * @return                  A locator that will locate a child or descendant node by traversing
     *                          the list of child indices.
     */
    public static IUi2Locator withChildIndex(int childIndex, int... descendantIndices) {
        return new ChildIndexUi2Locator(childIndex, descendantIndices);
    }

    /**
     * Locates the node(s) at a certain depth.
     *
     * @param depth The depth.
     * @return      A locator that will locate the child(ren) at the given depth.
     */
    public static IUi2Locator withChildDepth(int depth) {
        return new BySelectorUi2Locator(By.depth(depth));
    }

    /**
     * Locates the node(s) having content description from string resource id.
     *
     * @param stringId The string resource id.
     * @return         A locator that will find the node(s) with the given content
     *                 description string id.
     *
     * @see <a
     *         href="https://developer.android.com/reference/android/view/View.html#getContentDescription()">getContentDescription</a>
     */
    public static IUi2Locator withContentDescString(int stringId) {
        return new BySelectorUi2Locator(By.desc(getResourceStringName(stringId)));
    }

    /**
     * Locates the node(s) having an exact content description.
     *
     * @param desc The content description to match against.
     * @return     A locator that will find the node(s) with the given content description.
     *
     * @see <a
     *         href="https://developer.android.com/reference/android/view/View.html#getContentDescription()">getContentDescription</a>
     */
    public static IUi2Locator withContentDesc(@NonNull String desc) {
        return new BySelectorUi2Locator(By.desc(desc));
    }

    /**
     * Locates the node(s) having an exact text string.
     *
     * @param text The text to match.
     * @return     A locator that will find the node(s) having the given text.
     *
     * @see <a
     *         href="https://developer.android.com/reference/android/widget/TextView.html#getText()">getText</a>
     */
    public static IUi2Locator withText(@NonNull String text) {
        return new BySelectorUi2Locator(By.text(text));
    }

    /**
     * Locates the node(s) having a text string that matches a regex.
     *
     * @param textRegex The regular expression to match the text against.
     * @return          A locator that will find the node(s) with text that matches the
     *                  given regular expression.
     *
     * @see <a
     *         href="https://developer.android.com/reference/android/widget/TextView.html#getText()">getText</a>
     */
    public static IUi2Locator withTextRegex(@NonNull String textRegex) {
        return new BySelectorUi2Locator(By.text(Pattern.compile(textRegex)));
    }

    /**
     * Locates the node(s) having a text string that contains a substring.
     *
     * @param subText Substring to search in the text field.
     * @return        A locator that will find node(s) with text that contains the given string.
     *
     * @see <a
     *         href="https://developer.android.com/reference/android/widget/TextView.html#getText()">getText</a>
     */
    public static IUi2Locator withTextContaining(@NonNull String subText) {
        return new BySelectorUi2Locator((By.textContains(subText)));
    }

    /**
     * Locates the node(s) having a class name that matches a regex.
     *
     * @param regex Regular expression for the class name.
     * @return      A locator that will find node(s) with class name that matches
     *              the given regular expression.
     */
    public static IUi2Locator withClassRegex(@NonNull String regex) {
        return new BySelectorUi2Locator((By.clazz(Pattern.compile(regex))));
    }

    /**
     * Locates the ith node(s) found by another locator.
     *
     * @param index   The value of i.
     * @param locator First locator in the chain.
     * @return        A locator that will find ith node in the results of locator.
     */
    public static IUi2Locator withIndex(int index, @NonNull IUi2Locator locator) {
        return new IndexUi2Locator(index, locator);
    }

    /**
     * Locates the node(s) matching the chain of locators.
     *
     * @param locator            First locator in the chain.
     * @param additionalLocators Optional, additional locators in the chain.
     * @return                   A locator that will find node(s) mathcing the chain of
     *                           locators.
     */
    public static IUi2Locator withPath(
            @NonNull IUi2Locator locator, IUi2Locator... additionalLocators) {
        return new PathUi2Locator(locator, additionalLocators);
    }

    /**
     * Locates the node(s) having an exact package name.
     *
     * @param packageName Exact package name to match.
     * @return            A locator that will find node(s) having the packageName.
     */
    public static IUi2Locator withPackageName(@NonNull String packageName) {
        return new BySelectorUi2Locator(By.pkg(packageName));
    }

    /**
     * This converts the integer resource ids to string resource entry names so
     * that UIAutomator can be used to located them.
     *
     * @param id The layout resource id of the corresponding view node.
     * @return   String resource entry name for id.
     */
    private static String getResourceEntryName(@IdRes int id) {
        return getTargetResources().getResourceEntryName(id);
    }

    /**
     * This converts the integer resource ids to string resource entry names so
     * that UIAutomator can be used to located them.
     *
     * @param ids The layout resource id of the corresponding view node.
     * @return    Array of string resource entry names for ids.
     */
    private static String[] getResourceEntryNames(@IdRes int... ids) {
        String[] names = new String[(ids.length)];
        for (int i = 0; i < ids.length; i++) {
            names[i] = getResourceEntryName(ids[i]);
        }
        return names;
    }

    /**
     * This converts the integer string id to the localized string.
     *
     * @param idString The string id.
     * @return         String value of idString.
     */
    private static String getResourceStringName(int idString) {
        return getTargetResources().getString(idString);
    }

    private static Resources getTargetResources() {
        if (sMockResources == null) {
            return ApplicationProvider.getApplicationContext().getResources();
        } else {
            return sMockResources;
        }
    }
}
