/*
 * Copyright 2020 Advanced Software Technologies Lab at ETH Zurich, Switzerland
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.commands.monkey.ape.jev;

import java.util.ArrayList;
import java.util.List;

import com.android.commands.monkey.ape.naming.Name;
import com.android.commands.monkey.ape.naming.NamerFactory;
import com.android.commands.monkey.ape.tree.GUITreeNode;

/**
 * Provenance-aware identity for Jev concrete controls.
 *
 * <p>Jev cross-GUI concrete fullName must ignore ONLY the synthetic image-hash
 * text that APE generates at the bitmap hashing write in
 * {@link GUITreeNode#computeAndSetImageText(android.graphics.Bitmap)}
 * (recorded through {@link GUITreeNode#setGeneratedImageText(String)}). Real
 * text -- including text that literally looks like "#1234" -- content-desc,
 * index, ancestor naming, class/resource id, and action flags are never
 * altered, and the marker is never inferred from string content.
 *
 * <p>This helper does not mutate live nodes, cached Names, or global naming
 * configuration. For nodes whose own text and every ancestor carry no
 * synthetic image-hash provenance, {@link #fullName(GUITreeNode)} returns the
 * exact same cached Name instance the native full namer produces for the live
 * node.
 */
public final class JevIdentity {

    private JevIdentity() {
    }

    /**
     * Returns the node text, or the empty string when the current text is the
     * synthetic image-hash write. Ordinary text is returned exactly.
     */
    public static String text(GUITreeNode node) {
        if (node == null) {
            throw new IllegalArgumentException("node must not be null");
        }
        return node.isGeneratedImageText() ? "" : node.getText();
    }

    /**
     * Returns the node's full native name (type, text, index, ancestor path,
     * and action patch) with every synthetic image-hash text in the chain
     * normalized to the empty string. Ordinary nodes keep their names exactly.
     */
    public static Name fullName(GUITreeNode node) {
        if (node == null) {
            throw new IllegalArgumentException("node must not be null");
        }
        for (GUITreeNode n = node; n != null; n = n.getParent()) {
            if (n.isGeneratedImageText()) return NamerFactory.fullNamer().naming(shadowChain(node));
        }
        return NamerFactory.fullNamer().naming(node);
    }

    /**
     * Builds a throwaway copy of the node's ancestor chain (root to node,
     * inclusive) so the shared full namer can be evaluated without touching
     * the live nodes. Only the attributes the full namer reads are copied;
     * texts carrying synthetic image-hash provenance are replaced by the empty
     * string, everything else is preserved exactly.
     */
    private static GUITreeNode shadowChain(GUITreeNode node) {
        List<GUITreeNode> live = new ArrayList<GUITreeNode>();
        for (GUITreeNode n = node; n != null; n = n.getParent()) {
            live.add(n);
        }
        GUITreeNode shadowParent = null;
        for (int i = live.size() - 1; i >= 0; i--) {
            GUITreeNode source = live.get(i);
            GUITreeNode shadow = new GUITreeNode(shadowParent);
            shadow.setClassName(source.getClassName());
            shadow.setResourceID(source.getResourceID());
            shadow.setText(text(source));
            shadow.setContentDesc(source.getContentDesc());
            shadow.setIndex(source.getIndex());
            shadow.setEnabled(source.isEnabled());
            shadow.setCheckable(source.isCheckable());
            shadow.setClickable(source.isClickable());
            shadow.setLongClickable(source.isLongClickable());
            shadow.setScrollable(source.getScrollable());
            shadowParent = shadow;
        }
        return shadowParent;
    }
}
