package com.macaca.android.testing.server.xmlUtils;

/**
 * Created by xdf on 09/05/2017.
 */

import android.annotation.TargetApi;
import android.graphics.Rect;
import android.view.accessibility.AccessibilityNodeInfo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * A UiElement that gets attributes via the Accessibility API.
 */
@TargetApi(18)
public class UiAutomationElement extends UiElement<AccessibilityNodeInfo, UiAutomationElement> {

    private final Map<Attribute, Object> attributes;
    private final boolean visible;
    private final Rect visibleBounds;
    private final UiAutomationElement parent;
    private final List<UiAutomationElement> children;
    public final static Map<AccessibilityNodeInfo, UiAutomationElement>  map = new WeakHashMap<AccessibilityNodeInfo, UiAutomationElement>();

    /**
     * A snapshot of all attributes is taken at construction. The attributes of a
     * {@code UiAutomationElement} instance are immutable. If the underlying
     * {@link AccessibilityNodeInfo} is updated, a new {@code UiAutomationElement}
     * instance will be created in
     */
    protected UiAutomationElement( AccessibilityNodeInfo node,
                                   UiAutomationElement parent, int index) {
        this.node = node;
        this.parent = parent;

        Map<Attribute, Object> attribs = new EnumMap<Attribute, Object>(Attribute.class);

        put(attribs, Attribute.INDEX, index);
        put(attribs, Attribute.PACKAGE, charSequenceToString(node.getPackageName()));
        put(attribs, Attribute.CLASS, charSequenceToString(node.getClassName()));
        put(attribs, Attribute.TEXT, charSequenceToString(node.getText()));
        put(attribs, Attribute.CONTENT_DESC, charSequenceToString(node.getContentDescription()));
        put(attribs, Attribute.RESOURCE_ID, charSequenceToString(node.getViewIdResourceName()));
        put(attribs, Attribute.CHECKABLE, node.isCheckable());
        put(attribs, Attribute.CHECKED, node.isChecked());
        put(attribs, Attribute.CLICKABLE, node.isClickable());
        put(attribs, Attribute.ENABLED, node.isEnabled());
        put(attribs, Attribute.FOCUSABLE, node.isFocusable());
        put(attribs, Attribute.FOCUSED, node.isFocused());
        put(attribs, Attribute.LONG_CLICKABLE, node.isLongClickable());
        put(attribs, Attribute.PASSWORD, node.isPassword());
        put(attribs, Attribute.SCROLLABLE, node.isScrollable());
        if (node.getTextSelectionStart() >= 0
                && node.getTextSelectionStart() != node.getTextSelectionEnd()) {
            attribs.put(Attribute.SELECTION_START, node.getTextSelectionStart());
            attribs.put(Attribute.SELECTION_END, node.getTextSelectionEnd());
        }
        put(attribs, Attribute.SELECTED, node.isSelected());
        put(attribs, Attribute.BOUNDS, getBounds(node));
        attributes = Collections.unmodifiableMap(attribs);

        // Order matters as getVisibleBounds depends on visible
        visible = node.isVisibleToUser();
        visibleBounds = getVisibleBounds(node);
        List<UiAutomationElement> mutableChildren = buildChildren(node);
        this.children = mutableChildren == null ? null : Collections.unmodifiableList(mutableChildren);
    }

    protected UiAutomationElement(String hierarchyClassName,
                                  AccessibilityNodeInfo childNode, int index){
        this.parent = null;
        Map<Attribute, Object> attribs = new EnumMap<Attribute, Object>(Attribute.class);

        put(attribs, Attribute.INDEX, index);
        put(attribs, Attribute.CLASS, charSequenceToString(hierarchyClassName));
        put(attribs, Attribute.CHECKABLE, false);
        put(attribs, Attribute.CHECKED, false);
        put(attribs, Attribute.CLICKABLE, false);
        put(attribs, Attribute.ENABLED, false);
        put(attribs, Attribute.FOCUSABLE, false);
        put(attribs, Attribute.FOCUSED, false);
        put(attribs, Attribute.LONG_CLICKABLE, false);
        put(attribs, Attribute.PASSWORD, false);
        put(attribs, Attribute.SCROLLABLE, false);
        put(attribs, Attribute.SELECTED, false);

        this.attributes = Collections.unmodifiableMap(attribs);
        this.visible= true;
        this.visibleBounds = null;
        List<UiAutomationElement> mutableChildren =new ArrayList<UiAutomationElement>();
        mutableChildren.add(new UiAutomationElement(childNode, this /* parent UiAutomationElement*/, 0/* index */));
        this.children = mutableChildren;
    }

    private void put(Map<Attribute, Object> attribs, Attribute key, Object value) {
        if (value != null) {
            attribs.put(key, value);
        }
    }

    private List<UiAutomationElement> buildChildren(AccessibilityNodeInfo node) {
        List<UiAutomationElement> children;
        int childCount = node.getChildCount();
        if (childCount == 0) {
            children = null;
        } else {
            children = new ArrayList<UiAutomationElement>(childCount);
            for (int i = 0; i < childCount; i++) {
                AccessibilityNodeInfo child = node.getChild(i);
                if (child != null && child.isVisibleToUser()) {
                    children.add(this.getElement(child, this, i));
                }
            }
        }
        return children;
    }

    public static UiAutomationElement newRootElement(AccessibilityNodeInfo rawElement) {
        clearData();
        /**
         * Injecting root element as hierarchy and adding rawElement as a child.
         */
        UiAutomationElement rootElement = new UiAutomationElement("hierarchy" /*root element*/, rawElement /* child nodInfo */, 0 /* index */);
        return rootElement;
    }

    private static void clearData() {
        map.clear();
        XPathSelector.clearData();
    }

    public static UiAutomationElement getElement(AccessibilityNodeInfo rawElement, UiAutomationElement parent, int index) {
        UiAutomationElement element = map.get(rawElement);
        if (element == null) {
            element = new UiAutomationElement(rawElement, parent, index);
            map.put(rawElement, element);
        }
        return element;
    }

    private Rect getBounds(AccessibilityNodeInfo node) {
        Rect rect = new Rect();
        node.getBoundsInScreen(rect);
        return rect;
    }

    private Rect getVisibleBounds(AccessibilityNodeInfo node) {
        if (!visible) {
            return new Rect();
        }
        Rect visibleBounds = getBounds(this.node);
        UiAutomationElement parent = getParent();
        Rect parentBounds;
        while (parent != null && parent.node != null) {
            parentBounds = parent.getBounds(this.parent.node);
            visibleBounds.intersect(parentBounds);
            parent = parent.getParent();
        }
        return visibleBounds;
    }

    public UiAutomationElement getParent() {
        return parent;
    }

    @Override
    protected List<UiAutomationElement> getChildren() {
        if (children == null) {
            return Collections.emptyList();
        }
        return children;
    }

    @Override
    protected Map<Attribute, Object> getAttributes() {
        return attributes;
    }

    public static String charSequenceToString(CharSequence input) {
        return input == null ? null : input.toString();
    }
}