package net.northking.cctp.upperComputer.driver.ios.command.data;


import net.northking.cctp.upperComputer.driver.ios.packet.CodecUtils;
import net.northking.cctp.upperComputer.driver.ios.packet.ICommandData;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


public class SearchUiNodeData implements ICommandData {

    private String type = "Unknown";
    private String label = null;
    private String name = null;
    private String value = null;
    private String chain = null;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getChain() {
        return chain;
    }

    public void setChain(String chain) {
        this.chain = chain;
    }
    @Override
    public void buildFromInput(DataInput dataInput) throws IOException {
        type = CodecUtils.readText(dataInput);
        label = CodecUtils.readNullable(dataInput, CodecUtils::readText);
        name = CodecUtils.readNullable(dataInput,CodecUtils::readText);
        value = CodecUtils.readNullable(dataInput,CodecUtils::readText);
        chain = CodecUtils.readNullable(dataInput,CodecUtils::readText);
    }

    @Override
    public void encodeToOutput(DataOutput dataOutput) throws IOException {
        CodecUtils.writeText(dataOutput, type);
        CodecUtils.writeNullable(dataOutput, label, CodecUtils::writeText);
        CodecUtils.writeNullable(dataOutput, name, CodecUtils::writeText);
        CodecUtils.writeNullable(dataOutput, value, CodecUtils::writeText);
        CodecUtils.writeNullable(dataOutput, chain, CodecUtils::writeText);
    }

    @Override
    public String toString() {
        return "SearchUiNodeData{" +
                "type='" + type + '\'' +
                ", label='" + label + '\'' +
                ", name='" + name + '\'' +
                ", value='" + value + '\'' +
                ", chain='" + chain + '\'' +
                '}';
    }

    /**
     * 搜索链，头部为最底层节点，逐步向上
     */
    public static class SearchChain {
        /**
         * 本节点在父层子项中的索引
         */
        private int inParentIndex = -1;

        /**
         * 父层匹配到的的节点数量
         */
        private int parentMatchCount = 0;

        /**
         * 本节点在父层匹配到的节点的数组中的索引
         */
        private int searchIndex = -1;

        private final UiNodeData node;
        private SearchChain parent = null;

        public SearchChain(UiNodeData node) {
            this.node = node;
        }

        public SearchChain(UiNodeData node, SearchChain parent) {
            this.node = node;
            this.parent = parent;
        }

        public SearchChain(UiNodeData node, int inParentIndex, SearchChain parent) {
            this.inParentIndex = inParentIndex;
            this.node = node;
            this.parent = parent;
        }

        public int getSearchIndex() {
            return searchIndex;
        }

        public void setSearchIndex(int searchIndex) {
            this.searchIndex = searchIndex;
        }

        public UiNodeData getNode() {
            return node;
        }

        public SearchChain getParent() {
            return parent;
        }

        /**
         * 生成下一个节点，也就是UI节点的父类为搜索终点的搜锁链
         *
         * @return 是否生成了下一个节点，如果没有父类了，则返回false
         */
        public boolean createNext() {
            SearchChain topChain = this;
            while (topChain.parent != null) {
                topChain = topChain.parent;
            }
            UiNodeData topNodeParent = topChain.getNode().getParent();
            if (topNodeParent != null) {
                topChain.parent = new SearchChain(topNodeParent, topNodeParent.inParentIndex(), null);
                return true;
            }
            return false;
        }

        public int getInParentIndex() {
            return inParentIndex;
        }

        public boolean isFloor() {
            return parent == null;
        }

        private String nsPredicateSafeString(String text) {
            return text.replace("\"", "\\\"").replace("'", "\\'").replace("\n", "\\n");
        }

        public String toSearchString(String exitString) {
            String searchString = exitString;
            if (searchString == null) searchString = "";

            StringBuilder sb = new StringBuilder();
            sb.append("/XCUIElementType");
            sb.append(node.getType());
            if (node.isTextFieldHasValue()) {
                sb.append("[`");
                boolean hasField = false;
                if (node.getLabel() != null) {
                    hasField = true;
                    sb.append("label == \"");
                    sb.append(nsPredicateSafeString(node.getLabel()));
                    sb.append("\"");
                }
                if (node.getName() != null) {
                    if (hasField) {
                        sb.append(" AND ");
                    }
                    sb.append("name == \"");
                    sb.append(nsPredicateSafeString(node.getName()));
                    sb.append("\"");
                    hasField = true;
                }
                if (node.getValue() != null) {
                    if (hasField) {
                        sb.append(" AND ");
                    }
                    sb.append("value == \"");
                    sb.append(nsPredicateSafeString(node.getValue()));
                    sb.append("\"");
                }

                sb.append("`]");
            }
            if (searchIndex > -1 && parentMatchCount > 1) {
                sb.append("[");
                sb.append(searchIndex + 1);
                sb.append("]");
            }

            searchString = sb + searchString;
            if (parent == null) {
                searchString = "**" + searchString;
            } else {
                searchString = parent.toSearchString(searchString);
            }

            return searchString;
        }

        /**
         * 计算控件在上一层的子项文本字段查找结果中的位置
         * <br>
         * 由于生成链时就已经确保了最准确的自下而上的关系，顶节点在树中为唯一入口，因此只需要计算每个链节点所在父层查找结果的位置即可。
         */
        public void calculateSearchIndex() {
            if (node.getParent() == null) {
                searchIndex = -1;
                return;
            }
            List<UiNodeData> matchedNodes = new ArrayList<UiNodeData>();
            for (UiNodeData child : node.getParent().getChildren()) {
                if (node.equalSearchField(child)) {
                    matchedNodes.add(child);
                }
            }
            parentMatchCount = matchedNodes.size();
            searchIndex = matchedNodes.indexOf(node);
            if (parent != null) {
                parent.calculateSearchIndex();
            }
        }


        @Override
        public String toString() {
            return "SearchChain{" +
                    "inParentIndex=" + inParentIndex +
                    ", node=" + node.toSearchNode() +
                    ", parent=" + parent +
                    "}";
        }
    }
}
