package net.northking.cctp.element.mobile.utils.phoneUtils;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.img.ImgUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.ImmutableMap;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.TouchAction;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.touch.WaitOptions;
import io.appium.java_client.touch.offset.PointOption;
import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.IStepTarget;
import net.northking.cctp.element.core.exception.EnvironmentException;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.element.core.exception.ParamMistakeException;
import net.northking.cctp.element.mobile.constants.ConfigPath;
import net.northking.cctp.element.mobile.constants.HzBankOcrCode;
import net.northking.cctp.element.mobile.constants.UsingType;
import net.northking.cctp.element.mobile.entity.PointMessage;
import net.northking.cctp.element.mobile.utils.CommonUtils;
import net.northking.cctp.element.mobile.utils.HttpUtils;
import net.northking.cctp.element.mobile.utils.RandomUtil;
import net.northking.cctp.element.mobile.utils.hzBank.HzBankOcrUtils;
import org.openqa.selenium.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import sun.misc.BASE64Decoder;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

/**
 * 元素操作
 */
public class ElementUtil {


    private static final Logger logger = LoggerFactory.getLogger(ElementUtil.class);

    private static final ExecutorService executorService = Executors.newFixedThreadPool(20);

    private static final String ANDROID_XPATH_INIT = "//*[contains(@text,'%s') or contains(@content-desc,'%s')]";
    private static final String IOS_XPATH_INIT = "//*[contains(@value,'%s') or contains(@label,'%s')]";

    private static ExecutorService findElementService = new ThreadPoolExecutor(30, 30, 0l, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>());

    /**
     * 查找控件
     *
     * @param driver         设备驱动
     * @param controlTargets 控件的属性
     * @param timeout        超时
     * @param waitTimeout    默认超时时间
     * @param more           是否支持查询到多个
     * @param address        选择第几个
     * @param waitStatus     是否等待界面稳定
     * @param swipe          是否滑动
     * @param swipeCount     滑动次数
     * @return
     */
    public static WebElement findElement(AppiumDriver<WebElement> driver,
                                         List<IStepTarget> controlTargets,
                                         Integer timeout,
                                         Integer waitTimeout,
                                         boolean more,
                                         Integer address,
                                         boolean waitStatus,
                                         String swipe,
                                         Integer swipeCount) {
        if (null == driver) {
            logger.error("driver获取失败");
            throw new EnvironmentException("设备自动化驱动连接已断开，请检查驱动程序是否正常");
        }
        WebElement webElement = null;
        if (timeout > 120 || timeout <= 0) {
            timeout = waitTimeout;
        }
        List<WebElement> webElements = findElementsByXpath(driver, controlTargets, swipe, swipeCount, timeout);
        if (webElements == null) {
            return null;
        }
        if (webElements.size() == 1) {//匹配到单个元素
            webElement = webElements.get(0);
        } else if (webElements.size() > 1) {//匹配到多个元素
            if (more) {
                if (address <= webElements.size()) {
                    webElement = webElements.get(address - 1);
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }

        if (null != webElement) {
            logger.debug("查找到的元素名字是：{}", webElement.getText());
        }
        return webElement;
    }

    /**
     * 查找控件，
     * ①当有必选时，必须满足必选，找到多个才满足可选
     * ②当无必选时，可选只需要满足一个即可
     * ③忽略不需要考虑进去
     * ④priority  0-忽略,1-必选,2-可选
     *
     * @param driver
     * @param controlTargets
     * @param swipe
     * @param swipeCount
     * @param timeout
     * @return
     */
    public static List<WebElement> findElementsByXpath(AppiumDriver<WebElement> driver, List<IStepTarget> controlTargets, String swipe, Integer swipeCount, Integer timeout) {
        //转换查询条件为xpath
        String[] xpath = targetToXpath(driver, controlTargets);
        //开始查找控件
        List<WebElement> webElements = findElements(driver, xpath, swipe, swipeCount, timeout);
        return webElements;
    }

    private static String[] targetToXpath(AppiumDriver<WebElement> driver, List<IStepTarget> controlTargets) {
        String text = "@text=";
        String id = "@resource-id=";
        String elementClass = "@class=";
        if (driver instanceof IOSDriver) {
            text = "@label=";
            id = "@name=";
            elementClass = "@type=";
        }
        int flag = 0;
        List<String> concatList = new ArrayList<>();
        List<String> selectList = new ArrayList<>();
        String[] xpath = new String[3];   //xpath数组  0-必选 xpath 1-拼接必选 xpath 2-可选 xpath
        for (IStepTarget controlTarget : controlTargets) {
            /*if ("0".equals(controlTarget.getStrategy())) {  //忽略
                flag++;
            } else if ("1".equals(controlTarget.getStrategy())) {  //必选
                if ("xpath".equals(controlTarget.getUsing())) {
                    xpath[0] = controlTarget.getValue();
                } else if ("id".equals(controlTarget.getUsing())) {
                    concatList.add(id + "'" + controlTarget.getValue() + "'");
                } else if ("class".equals(controlTarget.getUsing())) {
                    concatList.add(elementClass + "'" + (driver instanceof IOSDriver?"XCUIElementType" + controlTarget.getValue():controlTarget.getValue())+ "'");
                } else {
                    concatList.add(text + "'" + controlTarget.getValue() + "'");
                }
            } else {   //可选
                if ("xpath".equals(controlTarget.getUsing())) {
                    xpath[2] = controlTarget.getValue();
                } else if ("id".equals(controlTarget.getUsing())) {
                    selectList.add("//*[" + id + "'" + controlTarget.getValue() + "']");
                } else if ("class".equals(controlTarget.getUsing())) {
                    selectList.add("//*[" + elementClass + "'" + controlTarget.getValue() + "']");
                } else {
                    selectList.add("//*[" + text + "'" + controlTarget.getValue() + "']");
                }
            }*/
        }
        if (flag >= 4) {
            throw new ParamMistakeException("查找控件的策略都为忽略，无法精准定位到控件");
        }
        if (!CollectionUtils.isEmpty(concatList)) {   //必选
            xpath[1] = "//*[" + concatList.stream().collect(Collectors.joining(" and ")) + "]";
        }
        if (!CollectionUtils.isEmpty(selectList)) {   //可选
            if (StringUtils.hasText(xpath[2])) {
                xpath[2] = xpath[2] + "|";
            }
            xpath[2] = (null != xpath[2] ? xpath[2] : "") + selectList.stream().collect(Collectors.joining(" | "));
        }
        logger.info("实际 xpath:{}", xpath[0]);
        logger.info("连接 xpath:{}", xpath[1]);
        logger.info("可选 xpath:{}", xpath[2]);
        return xpath;
    }

    private static List<WebElement> findElements(AppiumDriver<WebElement> driver, String[] xpath, String swipe, Integer swipeCount, Integer timeout) {
        int stepTimeout = timeout;
        //每屏查找时间
        if (!"0".equals(swipe)) {
            stepTimeout = timeout / (swipeCount + 1);
        }
        //开始查找
        int stepTimeoutFinal = stepTimeout;
        Future<List<WebElement>> futureTask = executorService.submit(() -> {
            int num = 1;
            List<WebElement> elements = null;
            do {
                driver.manage().timeouts().implicitlyWait(stepTimeoutFinal, TimeUnit.SECONDS);
                long startTime = System.currentTimeMillis();
                if (StringUtils.hasText(xpath[0])) {   //xpath必选
                    logger.info("xpath是必选，xpath:{}", xpath[0]);
                    elements = driver.findElementsByXPath(xpath[0]);
                    if (!CollectionUtils.isEmpty(elements)) {  //必选xpath有找到元素
                        logger.info("查找到{}个控件", elements.size());
                        Long spendTime = System.currentTimeMillis() - startTime;  //必选xpath查找元素耗时
                        driver.manage().timeouts().implicitlyWait(stepTimeoutFinal * 1000 - spendTime, TimeUnit.MILLISECONDS);  //设置剩下的超时时间
                        if (StringUtils.hasText(xpath[1])) {  //其他的条件也存在必选
                            logger.info("其他也存在必选的条件,组装成xpath：{}", xpath[1]);
                            List<WebElement> concatElements = driver.findElementsByXPath(xpath[1]);
                            if (!CollectionUtils.isEmpty(concatElements)) {    //其他的必选条件查找到控件，合并
                                logger.info("其他必选条件查到{}个元素", concatElements.size());
                                elements = mergeWebElement(elements, concatElements);
                                logger.info("合并完成之后一共{}个元素", elements.size());
                                //合并之后元素不为空
                                Long spendTime2 = System.currentTimeMillis() - startTime;
                                if (!CollectionUtils.isEmpty(elements)) {
                                    if (StringUtils.hasText(xpath[2])) {  //有可选,再继续查可选
                                        driver.manage().timeouts().implicitlyWait(stepTimeoutFinal * 1000 - spendTime2 / 1000, TimeUnit.MILLISECONDS);
                                        List<WebElement> selectElements = driver.findElementsByXPath(xpath[2]);
                                        if (!CollectionUtils.isEmpty(selectElements)) {  //找到了就合并
                                            List<WebElement> mergeElements = mergeWebElement(elements, selectElements);
                                            if (null != mergeElements) {  //合并完成，不是空，就返回
                                                elements = mergeElements;
                                            }
                                        }
                                    }
                                } else {  //为空没必要再查下去，把时间睡下去
                                    Thread.sleep(stepTimeoutFinal * 1000 - spendTime2);
                                }
                            } else {  //其他必选条件没有查找到元素
                                logger.info("其他必选条件没有查找到元素");
                                elements = null;
                                logger.debug("总共查找耗时:{}s", (System.currentTimeMillis() - startTime) / 1000);
                            }
                        } else {   //不存在其他必选条件,看看是否有可选条件
                            if (StringUtils.hasText(xpath[2])) {  //有可选条件
                                List<WebElement> selectElements = driver.findElementsByXPath(xpath[2]);
                                if (!CollectionUtils.isEmpty(selectElements)) {
                                    List<WebElement> mergeWebElements = mergeWebElement(elements, selectElements);
                                    if (!CollectionUtils.isEmpty(mergeWebElements)) {
                                        elements = mergeWebElements;
                                    }
                                }
                            }
                        }
                    } else {   //xpath必选没有找到元素
                        logger.debug("xpath必选没有找到元素，查找时间 :{}s", (System.currentTimeMillis() - startTime) / 1000);
                    }
                } else {  //xpath不是必选
                    if (StringUtils.hasText(xpath[1])) {  //其他条件存在必选
                        long startTimeFind = System.currentTimeMillis();
                        elements = driver.findElementsByXPath(xpath[1]);
                        logger.info("找控件所需时间：{}ms", System.currentTimeMillis() - startTimeFind);
                        if (!CollectionUtils.isEmpty(elements)) {  //其他必选条件有找到
                            logger.info("其他必选条件找到{}个元素", elements.size());
                            if (StringUtils.hasText(xpath[2])) {
                                logger.info("有可选条件，xpath：{}", xpath[2]);
                                long spendTime = System.currentTimeMillis() - startTime;
                                driver.manage().timeouts().implicitlyWait(stepTimeoutFinal * 1000 - spendTime, TimeUnit.MILLISECONDS);
                                List<WebElement> selectElements = driver.findElementsByXPath(xpath[2]);
                                if (!CollectionUtils.isEmpty(selectElements)) {
                                    List<WebElement> mergeElement = mergeWebElement(elements, selectElements);
                                    if (!CollectionUtils.isEmpty(mergeElement)) {
                                        elements = mergeElement;
                                    }
                                }
                            }
                        } else {
                            logger.info("其他得必选条件没找到元素，共计耗时：{}", (System.currentTimeMillis() - startTime) / 1000);
                        }
                    } else {  //其他条件也不是必选，那么全是可选
                        logger.info("没有必选条件，全是可选条件，xpath：{}", xpath[2]);
                        elements = driver.findElementsByXPath(xpath[2]);
                    }
                }

                if (!CollectionUtils.isEmpty(elements)) {
                    logger.info("最终符合条件的元素有{}个", elements.size());
                    for (int i = 0; i < elements.size(); i++) {
                        WebElement element = elements.get(i);
                        logger.info("第{}个元素名字：{},位置x:{},y:{}", i + 1, element.getText(), element.getRect().getX(), element.getRect().getY());
                    }
                    return elements;
                }
                if ("0".equals(swipe)) {  //不滑屏，直接返回找不到
                    logger.warn("不滑屏，没找到控件");
                    return null;
                }
                ScreenUtil.standardSwipe(swipe, 2000, driver);  //滑一下屏
                num++;
            } while (num <= swipeCount);
            logger.warn("找了一圈都没找到元素");
            return null;
        });
        try {
            List<WebElement> webElements = futureTask.get(500, TimeUnit.SECONDS);
            return webElements;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            logger.error("查找元素中断：", e);
        } catch (ExecutionException e) {
            logger.error("查找元素失败", e);
        } catch (TimeoutException e) {
            logger.error("查找元素超时", e);
            futureTask.cancel(true);
        }
        return null;
    }

    private static List<WebElement> mergeWebElement(List<WebElement> elements, List<WebElement> concatElements) {
        List<WebElement> webElements = new ArrayList<>();
        for (WebElement element : elements) {
            for (WebElement concatElement : concatElements) {
                long start = System.currentTimeMillis();
                if (element.getLocation().getX() == concatElement.getLocation().getX()
                        && element.getLocation().getY() == concatElement.getLocation().getY()
                ) {
                    webElements.add(element);
                    break;
                }
                logger.info("对比一个控件耗时:{}ms", System.currentTimeMillis() - start);
            }
        }
        return webElements;
    }


    /**
     * <p>查找页面上存在的文本</p>
     *
     * @param context
     * @param driver  driver链接
     * @param text    查找的文本
     * @return 查到包含文本的所有控件
     */
    public static List<PointMessage> findElementsByText(IExecuteContext context, AppiumDriver<WebElement> driver, String text) {
        List<PointMessage> points = new ArrayList<>();
        String ocrAddress = context.getProperty(ConfigPath.OCR_ADDRESS_KEY);
        String mobileShotAddress = context.getProperty(ConfigPath.MOBILE_SCREEN_SHOT_ADDRESS_KEY);
        Map<String, Object> deviceInfo = context.getDeviceInfo();
        String resolution = (String) deviceInfo.get("resolution");
        Map<String, Object> paramMap = new HashMap<>();
        if (driver instanceof IOSDriver) {
            paramMap.put("platform", "1");
        } else {
            paramMap.put("platform", "0");
        }
        paramMap.put("deviceId", driver.getCapabilities().getCapability("deviceName"));
        paramMap.put("resolution", resolution);
        logger.info("参数：{}", JSON.toJSONString(paramMap));
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(paramMap);
        String host = driver.getRemoteAddress().getHost();  //上位机地址
        String port = (String) deviceInfo.get("port");   //上位机端口
        String shotScreenAddress = String.format(ConfigPath.HTTP_FORMAT, host, port, mobileShotAddress);
        String result = null;
        try {
            result = HttpUtils.doPost(shotScreenAddress, entity, String.class);
        } catch (Exception e) {
            logger.error("截图失败", e);
        }
        if (null == result) {
            return null;
        }

        // 是否使用新版ocr
        boolean useNewOcr = Boolean.parseBoolean(context.getProperty(ConfigPath.USE_NEW_OCR));
        if (useNewOcr) {
            ocrAddress = context.getProperty(ConfigPath.OCR_GET_ALL_TEXT_NUM_KEY);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            Map<String, Object> ocrParamMap = new HashMap<>();
            ocrParamMap.put("img_base64", result);
            if (!StringUtils.hasText(text)) {
                throw new ExecuteException("点击的文本不能为空！");
            }
            ocrParamMap.put("targets", text);
            HttpEntity<Map<String, Object>> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
            List ocrResultList = null;
            try {
                logger.info("传参：{}", JSON.toJSONString(ocrParamMap));
                ocrResultList = HttpUtils.doPost(ocrAddress, ocrEntity, List.class);
                logger.info("得到的ocr结果：{}", ocrResultList);
            } catch (Exception e) {
                logger.error("ocr失败", e);
            }
            if (!CollectionUtils.isEmpty(ocrResultList)) {
                Object all = ocrResultList.get(0);
                JSONObject jsonObject = JSONObject.parseObject(JSONArray.toJSONString(all));
                //todo：如果传了;是多个文本一起找，暂时只考虑一个文本
                JSONArray data = jsonObject.getJSONArray(text);   //所有坐标
                if (!CollectionUtils.isEmpty(data)) {
                    if (data.size() == 1) {
                        JSONObject itemPoint = JSONObject.parseObject(JSONObject.toJSONString(data.get(0)));
                        PointMessage point = new PointMessage(itemPoint.getInteger("x") + (itemPoint.getInteger("w") / 2),
                                itemPoint.getInteger("y") + (itemPoint.getInteger("h") / 2), true);
                        points.add(point);
                    } else {
                        for (int i = 0; i < data.size(); i++) {
                            JSONObject itemPoint = JSONObject.parseObject(JSONObject.toJSONString(data.get(i)));
                            PointMessage point = new PointMessage(itemPoint.getInteger("x") + (itemPoint.getInteger("w") / 2),
                                    itemPoint.getInteger("y") + (itemPoint.getInteger("h") / 2), true);
                            points.add(point);
                        }
                    }
                }
            }
        } else {
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            Map<String, Object> ocrParamMap = new HashMap<>();
            ocrParamMap.put("base64Image", result);
            ocrParamMap.put("CaseSensitive", true);
            if (!StringUtils.hasText(text)) {
                throw new ExecuteException("查找的文本不能为空！");
            }
            ocrParamMap.put("ocrText", text);
            HttpEntity<Map<String, Object>> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
            JSONObject ocrResultMap = null;
            try {
                ocrResultMap = HttpUtils.doPost(ocrAddress, ocrEntity, JSONObject.class);
                logger.info("得到的ocr结果：{}", ocrResultMap);
            } catch (Exception e) {
                logger.error("ocr失败", e);
            }
            if (null != ocrResultMap) {
                Boolean success = ocrResultMap.getBoolean("success");
                if (success) {
                    //转换比例
                    String[] split = resolution.split("\\*");
                    int width = Integer.parseInt(split[0]);
                    int height = Integer.parseInt(split[1]);

                    int realityWidth = width;
                    int realityHeight = height;
                    if (width > height) {  //宽高反了的情况，调换
                        realityWidth = height;
                        realityHeight = width;
                    }
                    JSONArray data = ocrResultMap.getJSONArray("data");
                    if (!CollectionUtils.isEmpty(data)) {
                        logger.info("找到{}个符合的文字", data.size());
                        for (int i = 0; i < data.size(); i++) {
                            JSONObject pointMap = data.getJSONObject(i);
                            PointMessage point = new PointMessage(pointMap.getInteger("x"), pointMap.getInteger("y"), true);
                            points.add(point);
                        }
                    }
                }
            }
        }
        return points;
    }


    public static void elementSwipe(IExecuteContext context, PointMessage point, String direction, AppiumDriver<WebElement> driver, Integer size) {
        Double x1 = Double.parseDouble(point.getX() + "");
        Double y1 = Double.parseDouble(point.getY() + "");
        Double swipeSize = new Double(size + "");
        try {
            if (point.isExchange()) {
                if (driver instanceof IOSDriver) {
                    Double scale = IosUtil.getWindowsSize(context, driver);
                    x1 = x1 / scale;
                    y1 = y1 / scale;
                    swipeSize = size / scale;
                }
            }
        } catch (Exception e) {
            logger.error("获取ios宽高比列异常", e);
            throw new ExecuteException("获取ios屏幕宽高失败");
        }
        Double x2, y2;
        if ("up".equalsIgnoreCase(direction)) {
            x2 = x1;
            y2 = y1 - swipeSize;
        } else if ("down".equalsIgnoreCase(direction)) {
            x2 = x1;
            y2 = y1 + swipeSize;
        } else if ("left".equalsIgnoreCase(direction)) {
            x2 = x1 - swipeSize;
            y2 = y1;
        } else if ("right".equalsIgnoreCase(direction)) {
            x2 = x1 + swipeSize;
            y2 = y1;
        } else {
            throw new ParamMistakeException("滑动方向仅支持：up、down、left、right");
        }
        logger.debug("开始节点x:{} y:{},结束节点x:{},y:{}", x1, y1, x2, y2);
        int time = 0;
        if ((size - 500) > 0 && (size - 500) % 500 == 0) {
            time = (size - 500) / 500;
        } else if ((size - 500) > 0 && (size - 500) % 500 != 0) {
            time = (size - 500) / 500 + 1;
        }
        try {
            long sum = 2000L + time * 1000L;
            new TouchAction(driver).press(PointOption.point(x1.intValue(), y1.intValue()))
                    .waitAction(WaitOptions.waitOptions(Duration.ofMillis(sum)))
                    .moveTo(PointOption.point(x2.intValue(), y2.intValue()))
                    .release().perform();
        } catch (WebDriverException e) {
            if (e instanceof NoSuchSessionException) {
                throw e;
            } else {
                logger.warn("driver 抽风了。。。。。,重新来一次");
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException interruptedException) {
                    Thread.currentThread().interrupt();
                }
                long sum = 2000L + time * 1000L;
                new TouchAction(driver).press(PointOption.point(x1.intValue(), y1.intValue()))
                        .waitAction(WaitOptions.waitOptions(Duration.ofMillis(sum)))
                        .moveTo(PointOption.point(x2.intValue(), y2.intValue()))
                        .release().perform();
            }
        }

    }

    public static void elementSwipe(IExecuteContext context, PointMessage point, String direction, AppiumDriver<WebElement> driver) {
        int width = driver.manage().window().getSize().width;
        int height = driver.manage().window().getSize().height;
        //默认左右滑屏滑动屏幕的4/5
        int size = width * 4 / 5;
        if ("up".equals(direction) || "down".equals(direction)) {
            size = height * 3 / 5;
        }
        elementSwipe(context, point, direction, driver, size);
    }

    /**
     * 输入文本
     *
     * @param driver
     * @param webElement
     * @param text
     * @param type
     * @param count
     * @param clean
     */
    public static void inputText(IExecuteContext context, AppiumDriver driver, WebElement webElement, String text, String type, Integer count, boolean clean) {
        String o = "";
        //聚焦输入框
        //webElement.click();
        if ("text".equals(type)) {
            o = text;
        } else if ("string".equals(type) || "int".equals(type) || "chinese".equals(type) || "english".equals(type)) {
            o = RandomUtil.randomString(count, type);
        }
        if (driver instanceof AndroidDriver) {
            webElement.click();
            //清除文本原有的文本
            if (clean) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                }
                enhancedClearText(webElement);
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
            logger.info("android设备输入文本：{}", o);
            webElement.sendKeys(o);
        } else {
            webElement.click();
            logger.info("ios设备输入文本：{}", o);
            if (clean) {
                logger.debug("清空文本。。。。。。。。。。");
                webElement.clear();
            }
            IosUtil.sendKeys(o, 60, driver);
        }
    }

    private static void enhancedClearText(WebElement element) {
        element.clear();
        String content = element.getText();
        long start = content.length();
        if (start > 0) {
            logger.warn("当前控件文本无法清空");
        }
    }

    public static PointMessage findElement(IExecuteContext context, AppiumDriver<WebElement> driver, List<IStepTarget> targets, Integer waitTimeout, boolean waitStatus, String swipe, Integer swipeCount) {
        PointMessage point = null;
        if (!CollectionUtils.isEmpty(targets)) {
            int size = targets.size();
            int timeOut = waitTimeout / size;
            if (timeOut <= 0) {
                timeOut = 1;
            }
            logger.info("每一个找{}s", timeOut);
            for (IStepTarget target : targets) {
                StopWatch stopWatch = new StopWatch();
                stopWatch.start();
                switch (target.getUsing()) {
                    case UsingType.SELECTOR:
                        point = xpathFind(driver, target, timeOut, swipe, swipeCount);
                        break;
                    case UsingType.OCR:
                        point = ocrFind(target, driver, context, timeOut);
                        break;
                    case UsingType.HZOCR:
                        point = hzOcrFind(target, driver, context, timeOut);
                        break;
                    case UsingType.POINT:
                        point = pointFind(target, driver, context);
                        break;
                    case UsingType.IMAGE:
                        point = imageFind(context, driver, target, timeOut, swipe, swipeCount);
                        break;
                    default:
                        logger.warn("不支持的查找元素方式：{}", target.getUsing());
                        break;
                }
                stopWatch.stop();
                logger.info("{}找了{}s", target.getUsing(), stopWatch.getTotalTimeSeconds());
                if (point != null) {
                    return point;
                }
            }
        }
        logger.info("point是不是null?,{}", null == point);
        return point;
    }

    /**
     * 以图找图
     *
     * @param context
     * @param driver
     * @param target
     * @param waitTimeout
     * @param swipe
     * @param swipeCount
     */
    public static PointMessage imageFind(IExecuteContext context, AppiumDriver<WebElement> driver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount) {
        PointMessage point = null;
        String serverAddress = context.getProperty(ConfigPath.SERVER_URL);  //图片下载地址
        String picDownloadAddress = context.getProperty(ConfigPath.DOWNLOAD_PIC_ADDRESS);  //图片下载地址
        String findPicAddress = context.getProperty(ConfigPath.FIND_PIC_ADDRESS_KEY);  //找图片地址
        String screenShotAddress = context.getProperty(ConfigPath.MOBILE_SCREEN_SHOT_ADDRESS_KEY);  //截全屏地址
        JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class);
        Float faultRate = 0.8f;
        String imageUrl = value.getString("imgUri");
        String fileName = imageUrl.substring(imageUrl.lastIndexOf("_") + 1);
        Map<String, Object> deviceInfo = context.getDeviceInfo();
        String host = driver.getRemoteAddress().getHost();  //上位机地址
        String port = (String) deviceInfo.get("port");   //上位机端口
        String resolution = (String) deviceInfo.get("resolution");  //分辨率
        String url = serverAddress + picDownloadAddress + imageUrl;
        String shotAllScreenAddress = String.format(ConfigPath.HTTP_FORMAT, host, port, screenShotAddress);
        HttpUtils.downloadFileToLocal(url, System.getProperty("user.dir") + "/tempPic", fileName);
        File file = new File(System.getProperty("user.dir") + "/tempPic/" + fileName);
        String base64Img = Base64.encode(file);
        long startTime = System.currentTimeMillis();
        long endTime = startTime;
        // 是否使用新版ocr
        boolean useNewOcr = Boolean.parseBoolean(context.getProperty(ConfigPath.USE_NEW_OCR));
        if (useNewOcr) {
            Map<String, BigDecimal> area = null;
            while ((endTime - startTime) / 1000 < waitTimeout && null == area) {
                area = findAreaByImage(shotAllScreenAddress, findPicAddress, driver, waitTimeout, base64Img, faultRate, resolution, swipe, swipeCount);
                endTime = System.currentTimeMillis();
            }
            if (null != area) {
                try {
                    byte[] bytes = new BASE64Decoder().decodeBuffer(base64Img);
                    BufferedImage read = ImageIO.read(new ByteArrayInputStream(bytes));
                    int width = read.getWidth();
                    int height = read.getHeight();
                    point = new PointMessage(area.get("x").intValue() + width / 2
                            , area.get("y").intValue() + height / 2, true);
                } catch (IOException e) {
                    logger.error("转换base64图片失败：{}", e);
                }
            }
        } else {
            Map<String, Integer> area = null;
            while ((endTime - startTime) / 1000 < waitTimeout && null == area) {
                area = findAreaByImageOld(shotAllScreenAddress, findPicAddress, driver, waitTimeout, base64Img, faultRate, resolution, swipe, swipeCount);
                endTime = System.currentTimeMillis();
            }
            if (null != area) {
                try {
                    byte[] bytes = new BASE64Decoder().decodeBuffer(base64Img);
                    BufferedImage read = ImageIO.read(new ByteArrayInputStream(bytes));
                    int width = read.getWidth();
                    int height = read.getHeight();
                    point = new PointMessage(area.get("x") + width / 2
                            , area.get("y") + height / 2, true);
                } catch (IOException e) {
                    logger.error("转换base64图片失败：{}", e);
                }
            }
        }
        return point;
    }

    private static Map<String, BigDecimal> findAreaByImage(String shotAllScreenAddress, String findPicAddress, AppiumDriver<WebElement> driver, Integer waitTimeout, String base64Img, Float faultRate, String resolution, String swipe, Integer swipeCount) {
        Map<String, BigDecimal> resultMap = null;
        //是否等待界面稳定
        /*if (waitStatus) {
            try {
                wait.waitStable(driver, 3000, 30000);
            } catch (InterruptedException e) {
                logger.error("等待界面稳定出错",e);
            }
        }*/
        if (null == waitTimeout || waitTimeout < 0 || waitTimeout > 120) {
            waitTimeout = 30;
        }

        resultMap = snapshotToFindImage(shotAllScreenAddress, findPicAddress, driver, base64Img, faultRate, resolution);
        if (null != resultMap) {
            logger.info("找到区域位置为:{}", JSON.toJSONString(resultMap));
            return resultMap;
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            logger.error("", e);
        }
        if (null == resultMap) {   //超过时间没找到
            logger.warn("指定时间内没有找到图片区域");
            if (!"0".equals(swipe)) {
                for (int i = 0; i < swipeCount; i++) {
                    logger.debug("尝试滑屏第{}次,滑屏方向：{}", i + 1, swipe);
                    try {
                        ScreenUtil.standardSwipe(swipe, 2000, driver);
                    } catch (Exception e) {
                        logger.error("滑屏出错：{}", e);
                        throw new ExecuteException("尝试滑屏出错");
                    }

                    resultMap = snapshotToFindImage(shotAllScreenAddress, findPicAddress, driver, base64Img, faultRate, resolution);
                    if (null != resultMap) {
                        logger.info("找到区域位置为:{}", JSON.toJSONString(resultMap));
                        return resultMap;
                    }
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        logger.error("", e);
                    }
                }
            } else {
                logger.debug("不尝试滑屏");
            }
        }
        return resultMap;
    }

    private static Map<String, Integer> findAreaByImageOld(String shotAllScreenAddress, String findPicAddress, AppiumDriver<WebElement> driver, Integer waitTimeout, String base64Img, Float faultRate, String resolution, String swipe, Integer swipeCount) {
        Map<String, Integer> resultMap = null;
        //是否等待界面稳定
        /*if (waitStatus) {
            try {
                wait.waitStable(driver, 3000, 30000);
            } catch (InterruptedException e) {
                logger.error("等待界面稳定出错",e);
            }
        }*/
        if (null == waitTimeout || waitTimeout < 0 || waitTimeout > 120) {
            waitTimeout = 30;
        }

        resultMap = snapshotToFindImageOld(shotAllScreenAddress, findPicAddress, driver, base64Img, faultRate, resolution);
        if (null != resultMap) {
            logger.info("找到区域位置为:{}", JSON.toJSONString(resultMap));
            return resultMap;
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            logger.error("", e);
        }
        if (null == resultMap) {   //超过时间没找到
            logger.warn("指定时间内没有找到图片区域");
            if (!"0".equals(swipe)) {
                for (int i = 0; i < swipeCount; i++) {
                    logger.debug("尝试滑屏第{}次,滑屏方向：{}", i + 1, swipe);
                    try {
                        ScreenUtil.standardSwipe(swipe, 2000, driver);
                    } catch (Exception e) {
                        logger.error("滑屏出错：{}", e);
                        throw new ExecuteException("尝试滑屏出错");
                    }

                    resultMap = snapshotToFindImageOld(shotAllScreenAddress, findPicAddress, driver, base64Img, faultRate, resolution);
                    if (null != resultMap) {
                        logger.info("找到区域位置为:{}", JSON.toJSONString(resultMap));
                        return resultMap;
                    }
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        logger.error("", e);
                    }
                }
            } else {
                logger.debug("不尝试滑屏");
            }
        }
        return resultMap;
    }

    private static Map<String, BigDecimal> snapshotToFindImage(String shotAllScreenAddress, String findPicAddress, AppiumDriver<WebElement> driver, String imgBase64, Float faultRate, String resolution) {
        String screenBase64 = null;
        Map<String, BigDecimal> resultPoint = null;
        String deviceId = (String) driver.getCapabilities().getCapability("deviceName");
        Map<String, String> screenParam = new HashMap<>();
        screenParam.put("deviceId", deviceId);
        screenParam.put("resolution", resolution);
        if (driver instanceof AndroidDriver) {
            screenParam.put("platform", "0");
        } else if (driver instanceof IOSDriver) {
            screenParam.put("platform", "1");
        }
        HttpEntity<Map> screenEntity = new HttpEntity<>(screenParam, null);
        screenBase64 = HttpUtils.doPost(shotAllScreenAddress, screenEntity, String.class);
        if (!StringUtils.hasText(screenBase64)) {
            throw new ExecuteException("当前页面截图失败，请确定当前页面是否支持截屏");
        }
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        Map<String, Object> param = new HashMap<>();
        param.put("sourceImage", screenBase64);
        param.put("targetImage", imgBase64);
        param.put("matchingThreshold", faultRate);
        HttpEntity<Map> httpEntity = new HttpEntity<>(param, headers);
        try {
            logger.debug("请求图像解析服务地址:{}", findPicAddress);
            logger.debug("请求图像解析服务参数:{}", JSON.toJSONString(param));
            URI uri = new URI(findPicAddress);
            Map body = HttpUtils.doPost(uri, httpEntity, Map.class);
            logger.debug("以图找图返回结果：{}", JSON.toJSONString(body));
            Map<String, Object> bodyMap = JSONObject.parseObject(JSON.toJSONString(body)).getInnerMap();
            if (Boolean.parseBoolean(String.valueOf(bodyMap.get("success")))) {
               resultPoint = (Map<String, BigDecimal>) bodyMap.get("data");
            }
            return resultPoint;
        } catch (URISyntaxException e) {
            logger.error("找图像地址有误,地址：{}，错误:{}", findPicAddress, e);
            throw new ParamMistakeException(String.format("图像解析地址[%s]有误", findPicAddress));
        } catch (Exception e) {
            logger.error("请求图像解析服务失败,地址：{},参数:{},错误:{}", findPicAddress, JSON.toJSONString(param), e);
            throw new ParamMistakeException("请求图像解析服务失败,请检查图像解析服务是否可用");
        }
    }

    private static Map<String, Integer> snapshotToFindImageOld(String shotAllScreenAddress, String findPicAddress, AppiumDriver<WebElement> driver, String imgBase64, Float faultRate, String resolution) {
        String screenBase64 = null;
        Map<String, Integer> resultPoint = null;
        String deviceId = (String) driver.getCapabilities().getCapability("deviceName");
        Map<String, String> screenParam = new HashMap<>();
        screenParam.put("deviceId", deviceId);
        screenParam.put("resolution", resolution);
        if (driver instanceof AndroidDriver) {
            screenParam.put("platform", "0");
        } else if (driver instanceof IOSDriver) {
            screenParam.put("platform", "1");
        }
        HttpEntity<Map> screenEntity = new HttpEntity<>(screenParam, null);
        screenBase64 = HttpUtils.doPost(shotAllScreenAddress, screenEntity, String.class);
        if (!StringUtils.hasText(screenBase64)) {
            throw new ExecuteException("当前页面截图失败，请确定当前页面是否支持截屏");
        }
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        Map<String, Object> param = new HashMap<>();
        param.put("sourceImage", screenBase64);
        param.put("targetImage", imgBase64);
        param.put("matchingThreshold", faultRate);
        HttpEntity<Map> httpEntity = new HttpEntity<>(param, headers);
        try {
            logger.debug("请求图像解析服务地址:{}", findPicAddress);
            logger.debug("请求图像解析服务参数:{}", JSON.toJSONString(param));
            URI uri = new URI(findPicAddress);
            Map body = HttpUtils.doPost(uri, httpEntity, Map.class);
            logger.debug("以图找图返回结果：{}", JSON.toJSONString(body));
            Map<String, Object> bodyMap = JSONObject.parseObject(JSON.toJSONString(body)).getInnerMap();
            if (Boolean.parseBoolean(String.valueOf(bodyMap.get("success")))) {
                resultPoint = (Map<String, Integer>) bodyMap.get("data");
            }
            return resultPoint;
        } catch (URISyntaxException e) {
            logger.error("找图像地址有误,地址：{}，错误:{}", findPicAddress, e);
            throw new ParamMistakeException(String.format("图像解析地址[%s]有误", findPicAddress));
        } catch (Exception e) {
            logger.error("请求图像解析服务失败,地址：{},参数:{},错误:{}", findPicAddress, JSON.toJSONString(param), e);
            throw new ParamMistakeException("请求图像解析服务失败,请检查图像解析服务是否可用");
        }
    }


    /**
     * ios截屏
     *
     * @param driver
     * @return
     */
    public static File snapshot(AppiumDriver<WebElement> driver) {
        File file = driver.getScreenshotAs(OutputType.FILE);
        logger.info("snapshot file:{}", file.getAbsolutePath());
        return file;
    }

    /**
     * 坐标查找
     *
     * @param target
     * @param driver
     * @param context
     */
    private static PointMessage pointFind(IStepTarget target, AppiumDriver<WebElement> driver, IExecuteContext context) {
        PointMessage point = null;
        JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class);
        Integer x = null;
        Integer y = null;
        Integer screenWidth = null;
        Integer screenHeight = null;
        boolean overTurn = false;
        try {
            x = value.getInteger("pointX");
            y = value.getInteger("pointY");
            screenWidth = value.getInteger("screenWidth");
            screenHeight = value.getInteger("screenHeight");
            if (screenWidth > screenHeight) {
                overTurn = true;
            }
        } catch (Exception e) {
            logger.error("坐标参数不对", e);
            return null;
        }
        Map<String, Object> deviceInfo = context.getDeviceInfo();
        String resolution = (String) deviceInfo.get("resolution");
        //转换比例
        String[] split = resolution.split("\\*");
        int width = Integer.parseInt(split[0]);
        int height = Integer.parseInt(split[1]);

        int realityWidth = width;
        int realityHeight = height;
        if (overTurn) {  //宽高反了的情况，调换
            if (width < height) {
                realityWidth = height;
                realityHeight = width;
            }
        } else {
            if (width > height) {
                realityWidth = height;
                realityHeight = width;
            }
        }
        Double realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
        Double realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
        if (realityX <= 0 || realityX >= realityWidth || realityY <= 0 || realityY >= realityHeight) {
            logger.warn("坐标方式超出了屏幕范围");
            return null;
        }
        /*if (realityY <= 0 || realityY >= realityHeight) {
            throw new ParamMistakeException("y坐标超出了屏幕范围");
        }*/
        point = new PointMessage(realityX.intValue(), realityY.intValue(), true);
        return point;
    }

    /**
     * ocr找元素
     *
     * @param target
     * @param driver
     * @param timeOut
     */
    private static PointMessage ocrFind(IStepTarget target, AppiumDriver<WebElement> driver, IExecuteContext context, int timeOut) {
        String ocrAddress = context.getProperty(ConfigPath.OCR_ADDRESS_KEY);
        String mobileShotAddress = context.getProperty(ConfigPath.MOBILE_SHOT_ADDRESS_KEY);
        PointMessage point = null;
        boolean[] rotation = new boolean[1];  //默认是正常手机的宽高
        rotation[0] = true;
        JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class);
        Integer x = value.getInteger("x");
        Integer y = value.getInteger("y");
        Integer screenWidth = value.getInteger("screenWidth");
        Integer screenHeight = value.getInteger("screenHeight");
        Map<String, Object> deviceInfo = context.getDeviceInfo();
        String resolution = (String) deviceInfo.get("resolution");
        Map<String, Object> paramMap = new HashMap<>();
        if (screenWidth > screenHeight) {
            rotation[0] = false;
        }
        if (driver instanceof IOSDriver) {
            paramMap.put("platform", "1");
        } else {
            paramMap.put("platform", "0");
        }
        paramMap.put("deviceId", driver.getCapabilities().getCapability("deviceName"));
        paramMap.put("length", value.getInteger("width"));
        paramMap.put("width", value.getInteger("height"));
        paramMap.put("resolution", resolution);
        paramMap.put("x", x);
        paramMap.put("y", y);
        paramMap.put("screenLength", screenWidth);
        paramMap.put("screenWidth", screenHeight);
        logger.info("参数：{}", JSON.toJSONString(paramMap));
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(paramMap);
        String host = driver.getRemoteAddress().getHost();  //上位机地址
        String port = (String) deviceInfo.get("port");   //上位机端口
        String shotScreenAddress = String.format(ConfigPath.HTTP_FORMAT, host, port, mobileShotAddress);
        long startTime = System.currentTimeMillis();
        long endTime = startTime;
        PointMessage pointMessage = null;
        while ((endTime - startTime) / 1000 < timeOut && null == pointMessage) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start("screen_shot");
            String result = null;
            try {
                result = HttpUtils.doPost(shotScreenAddress, entity, String.class);
            } catch (Exception e) {
                logger.error("截图失败", e);
            }
            stopWatch.stop();
            logger.debug("截图时间：{}s", stopWatch.getTotalTimeSeconds());
            if (null == result) {
                return null;
            }

            // 是否使用新版ocr
            boolean useNewOcr = Boolean.parseBoolean(context.getProperty(ConfigPath.USE_NEW_OCR));
            if (useNewOcr) {
                ocrAddress = context.getProperty(ConfigPath.OCR_GET_ALL_TEXT_NUM_KEY);
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                String ocrText = value.getString("ocrText");
                Integer index = value.getInteger("index");
                Map<String, Object> ocrParamMap = new HashMap<>();
                ocrParamMap.put("img_base64", result);
                if (!StringUtils.hasText(ocrText)) {
                    throw new ExecuteException("点击的文本不能为空！");
                }
                ocrParamMap.put("targets", ocrText);
                HttpEntity<Map<String, Object>> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
                List ocrResultList = null;
                try {
                    logger.info("传参：{}", JSON.toJSONString(ocrParamMap));
                    ocrResultList = HttpUtils.doPost(ocrAddress, ocrEntity, List.class);
                    logger.info("得到的ocr结果：{}", ocrResultList);
                } catch (Exception e) {
                    logger.error("ocr失败", e);
                }
                if (!CollectionUtils.isEmpty(ocrResultList)) {
                    String[] split = resolution.split("\\*");
                    int width = Integer.parseInt(split[0]);
                    int height = Integer.parseInt(split[1]);
                    int realityWidth = width;
                    int realityHeight = height;
                    if (rotation[0]) {   //正常
                        if (width > height) {  //宽大于高，调换
                            realityWidth = height;
                            realityHeight = width;
                        }
                    } else {  //不正常，横屏
                        if (width < height) {  //宽小于高，调换
                            realityWidth = height;
                            realityHeight = width;
                        }
                    }
                    Double realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
                    Double realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
                    Object all = ocrResultList.get(0);
                    JSONObject jsonObject = JSONObject.parseObject(JSONArray.toJSONString(all));
                    //todo：如果传了;是多个文本一起找，暂时只考虑一个文本
                    JSONArray data = jsonObject.getJSONArray(ocrText);   //所有坐标
                    if (!CollectionUtils.isEmpty(data)) {
                        if (data.size() == 1) {
                            JSONObject itemPoint = JSONObject.parseObject(JSONObject.toJSONString(data.get(0)));
                            pointMessage = new PointMessage(itemPoint.getInteger("x") + (itemPoint.getInteger("w") / 2)+realityX.intValue(),
                                    itemPoint.getInteger("y") + (itemPoint.getInteger("h") / 2)+realityY.intValue(), true);
                        } else {
                            if (index > 0) {
                                if (index <= data.size()) {
                                    JSONObject itemPoint = JSONObject.parseObject(JSONObject.toJSONString(data.get(index - 1)));
                                    pointMessage = new PointMessage(itemPoint.getInteger("x") + (itemPoint.getInteger("w") / 2)+realityX.intValue(),
                                            itemPoint.getInteger("y") + (itemPoint.getInteger("h") / 2)+realityY.intValue(), true);
//                                    pointMessage = sortPointAndGetIndexPoint(data, index);
                                } else {
                                    throw new ExecuteException(String.format("选择元素的位置大于找到元素的个数，找到元素%s个", data.size()));
                                }
                            }
                        }
                    }
                }
                endTime = System.currentTimeMillis();
            } else {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                Map<String, Object> ocrParamMap = new HashMap<>();
                ocrParamMap.put("base64Image", result);
                ocrParamMap.put("CaseSensitive", (null == value.getBoolean("CaseSensitive")) ? false : value.getBoolean("CaseSensitive"));
                if (!StringUtils.hasText(value.getString("ocrText"))) {
                    throw new ExecuteException("ocr查找的文本不能为空！");
                }
                ocrParamMap.put("ocrText", value.getString("ocrText"));
                HttpEntity<Map<String, Object>> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
                JSONObject ocrResultMap = null;
                stopWatch.start("ocr");
                try {
                    ocrResultMap = HttpUtils.doPost(ocrAddress, ocrEntity, JSONObject.class);
                    logger.info("得到的ocr结果：{}", ocrResultMap);
                } catch (Exception e) {
                    logger.error("ocr失败", e);
                }
                stopWatch.stop();
                logger.debug("ocr时间：{}ms", stopWatch.getLastTaskTimeMillis());
                if (null != ocrResultMap) {
                    Boolean success = ocrResultMap.getBoolean("success");
                    if (success) {
                        //转换比例
                        String[] split = resolution.split("\\*");
                        int width = Integer.parseInt(split[0]);
                        int height = Integer.parseInt(split[1]);
                        int realityWidth = width;
                        int realityHeight = height;
                        if (rotation[0]) {   //正常
                            if (width > height) {  //宽大于高，调换
                                realityWidth = height;
                                realityHeight = width;
                            }
                        } else {  //不正常，横屏
                            if (width < height) {  //宽小于高，调换
                                realityWidth = height;
                                realityHeight = width;
                            }
                        }
                        Double realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
                        Double realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
                        JSONArray data = ocrResultMap.getJSONArray("data");
                        Integer index = value.getInteger("index");
                        JSONObject pointMap = null;
                        if (data != null && data.size() == 1) {   //只找到一个，直接返回
                            pointMap = data.getJSONObject(0);
                        } else if (data != null && data.size() > 1) {   //找到多个，选择用户选择的第几个
                            if (index > 0) {
                                if (index <= data.size()) {
                                    pointMap = data.getJSONObject(index - 1);
                                } else {
                                    throw new ExecuteException(String.format("选择元素的位置大于找到元素的个数，找到元素%s个", data.size()));
                                }
                            }
                        }
                        if (null != pointMap) {
                            pointMessage = new PointMessage(realityX.intValue() + pointMap.getInteger("x"), realityY.intValue() + pointMap.getInteger("y"), true);
                        }
                    }
                }
                endTime = System.currentTimeMillis();
            }
        }
        return pointMessage;
    }

    /**
     * 杭银ocr找元素
     *
     * @param target
     * @param driver
     * @param timeOut
     */
    private static PointMessage hzOcrFind(IStepTarget target, AppiumDriver<WebElement> driver, IExecuteContext context, int timeOut) {
        String ocrAddress = context.getProperty(ConfigPath.OCR_ADDRESS_KEY);
        String mobileShotAddress = context.getProperty(ConfigPath.MOBILE_SHOT_ADDRESS_KEY);
        PointMessage point = null;
        boolean[] rotation = new boolean[1];  //默认是正常手机的宽高
        rotation[0] = true;
        JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class);
        Integer x = value.getInteger("x");
        Integer y = value.getInteger("y");
        Integer screenWidth = value.getInteger("screenWidth");
        Integer screenHeight = value.getInteger("screenHeight");
        Map<String, Object> deviceInfo = context.getDeviceInfo();
        String resolution = (String) deviceInfo.get("resolution");
        Map<String, Object> paramMap = new HashMap<>();
        if (screenWidth > screenHeight) {
            rotation[0] = false;
        }
        if (driver instanceof IOSDriver) {
            paramMap.put("platform", "1");
        } else {
            paramMap.put("platform", "0");
        }
        paramMap.put("deviceId", driver.getCapabilities().getCapability("deviceName"));
        paramMap.put("length", value.getInteger("width"));
        paramMap.put("width", value.getInteger("height"));
        paramMap.put("resolution", resolution);
        paramMap.put("x", x);
        paramMap.put("y", y);
        paramMap.put("screenLength", screenWidth);
        paramMap.put("screenWidth", screenHeight);
        logger.info("参数：{}", JSON.toJSONString(paramMap));
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(paramMap);
        String host = driver.getRemoteAddress().getHost();  //上位机地址
        String port = (String) deviceInfo.get("port");   //上位机端口
        String shotScreenAddress = String.format(ConfigPath.HTTP_FORMAT, host, port, mobileShotAddress);
        long startTime = System.currentTimeMillis();
        long endTime = startTime;
        PointMessage pointMessage = null;
        while ((endTime - startTime) / 1000 < timeOut && null == pointMessage) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start("screen_shot");
            String result = null;
            try {
                result = HttpUtils.doPost(shotScreenAddress, entity, String.class);
            } catch (Exception e) {
                logger.error("截图失败", e);
            }
            stopWatch.stop();
            logger.debug("截图时间：{}s", stopWatch.getTotalTimeSeconds());
            if (null == result) {
                return null;
            }
            String textByHzOcr;
            try {
                textByHzOcr = HzBankOcrUtils.getTextByHzOcr(result, context);
            } catch (Exception e) {
                logger.error("查找杭银ocr失败，原因为： {}", e);
                throw e;
            }
            //转换比例
            String[] split = resolution.split("\\*");
            int width = Integer.parseInt(split[0]);
            int height = Integer.parseInt(split[1]);
            int realityWidth = width;
            int realityHeight = height;
            if (rotation[0]) {   //正常
                if (width > height) {  //宽大于高，调换
                    realityWidth = height;
                    realityHeight = width;
                }
            } else {  //不正常，横屏
                if (width < height) {  //宽小于高，调换
                    realityWidth = height;
                    realityHeight = width;
                }
            }
            Double realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
            Double realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
            List<PointMessage> data = HzBankOcrUtils.getAllTextPointByData(textByHzOcr, value.getString("ocrText"));
            Integer index = value.getInteger("index");
            PointMessage pointMap = null;
            if (data != null && data.size() == 1) {   //只找到一个，直接返回
                pointMap = data.get(0);
            } else if (data != null && data.size() > 1) {   //找到多个，选择用户选择的第几个
                if (index > 0) {
                    if (index <= data.size()) {
                        pointMap = data.get(index - 1);
                    } else {
                        throw new ExecuteException(String.format("选择元素的位置大于找到元素的个数，找到元素%s个", data.size()));
                    }
                }
            }
            if (null != pointMap) {
                pointMessage = new PointMessage(realityX.intValue() + pointMap.getX(), realityY.intValue() + pointMap.getY(), true);
            }
            endTime = System.currentTimeMillis();
        }
        return pointMessage;
    }

    /**
     * xpath找元素
     *
     * @param driver
     * @param target
     * @param waitTimeout
     * @param swipe
     * @param swipeCount
     */
    private static PointMessage xpathFind(AppiumDriver<WebElement> driver, IStepTarget target, Integer
            waitTimeout, String swipe, Integer swipeCount) {
        PointMessage point = null;
        JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class);
        String xpath = object.getString(UsingType.Key.SELECTOR_KEY);
        logger.info("拿到的xpath:{}", xpath);

        Integer perPageTimeout = waitTimeout;
        Integer swipeNum = swipeCount;
        if ((swipeCount <= 0 || swipeCount > 5)/*限定滑屏查找不超过5次*/ || "0".equals(swipe)  /*不滑屏*/) {
            swipeNum = 0;
        }
        if (swipeNum > 0) {
            perPageTimeout = waitTimeout / swipeCount;
        }
        try {
            driver.manage().timeouts().implicitlyWait(perPageTimeout, TimeUnit.SECONDS);
        } catch (NoSuchSessionException e) {
            throw e;
        }
        WebElement webElement = getWebElement(driver, swipe, xpath, swipeNum);
        Rectangle rect = null;
        try {
            rect = webElement.getRect();
        } catch (StaleElementReferenceException e) {
            logger.warn("页面发生变化，重新查找元素", e);
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException ex) {
                logger.error("在查找元素等待时被打断", ex);
            }
            logger.info("再次查找当前页面的元素");
            WebElement we = getWebElement(driver, swipe, xpath, swipeNum);
            if (null != we) {
                rect = we.getRect();
            }
        }
        if (null != rect) {
            int x = rect.getX();
            int y = rect.getY();
            int width = rect.getWidth();
            int height = rect.getHeight();
            point = new PointMessage(x + width / 2, y + height / 2, false);
        }
        return point;
    }

    private static WebElement getWebElement(AppiumDriver<WebElement> driver, String swipe, String xpath, Integer swipeNum) {
        WebElement webElement = null;
        while (swipeNum >= 0) {
            List<WebElement> elements = null;
            try {
                elements = driver.findElements(By.xpath(xpath));
            } catch (WebDriverException e) {
                logger.error("driver查找控件异常：", e);
                throw e;
            }
            logger.info("找到元素{}个", elements.size());
            if (!CollectionUtils.isEmpty(elements)) {   //xpath找到了
                webElement = elements.get(0);
            } else {  //xpath没找到
                // 输出当前页面的ui树
                logger.debug("当前页面的信息：{}", driver.getPageSource());
                if (swipeNum > 0) {
                    ScreenUtil.standardSwipe(swipe, 2000, driver);
                }
            }
            swipeNum--;
        }
        return webElement;
    }


    public static WebElement findWebElementByXpath(AppiumDriver<WebElement> driver, IStepTarget target, Integer waitTimeout, boolean waitStatus, String swipe, Integer swipeCount) {
        JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class);
        String xpath = object.getString(UsingType.Key.SELECTOR_KEY);
        logger.info("拿到的xpath:{}", xpath);

        Integer perPageTimeout = waitTimeout;
        Integer swipeNum = swipeCount;
        if ((swipeCount <= 0 && swipeCount > 5)/*限定滑屏查找不超过5次*/ || "0".equals(swipe)  /*不滑屏*/) {
            swipeNum = 0;
        }
        if (swipeNum > 0) {
            perPageTimeout = waitTimeout / swipeCount;
        }
        try {
            driver.manage().timeouts().implicitlyWait(perPageTimeout, TimeUnit.SECONDS);
        } catch (NoSuchSessionException e) {
            throw e;
        }
        WebElement webElement = null;
        while (swipeNum >= 0) {
            List<WebElement> elements = null;
            try {
                elements = driver.findElements(By.xpath(xpath));
            } catch (NoSuchSessionException e) {
                throw e;
            }
            logger.info("找到元素{}个", elements.size());
            if (!CollectionUtils.isEmpty(elements)) {   //xpath找到了
                webElement = elements.get(0);
            } else {  //xpath没找到
                if (swipeNum > 0) {
                    ScreenUtil.standardSwipe(swipe, 2000, driver);
                }
            }
            swipeNum--;
        }
        return webElement;
    }

    public static String getOcrAreaBase64(IExecuteContext context, AppiumDriver<WebElement> driver, List<IStepTarget> targets, Integer timeout, boolean waitStatus) {
        if (CollectionUtils.isEmpty(targets)) {
            throw new ParamMistakeException("定位元素参数不能为空");
        }
        String result = null;
        for (IStepTarget target : targets) {
            switch (target.getUsing()) {
                case UsingType.SELECTOR:
                    result = xpathFindCodeArea(context, driver, target, timeout, waitStatus);
                    break;
                case UsingType.OCR:
                    result = ocrFindCodeArea(target, driver, context);
                    break;
                case UsingType.HZOCR:
                    result = ocrFindCodeArea(target, driver, context);
                    break;
                case UsingType.POINT:
                    logger.warn("不支持验证码识别的方式：{}", target.getUsing());
                    throw new ExecuteException("不支持坐标的方式识别验证码");
                case UsingType.IMAGE:
                    logger.warn("不支持验证码识别的方式：{}", target.getUsing());
                    throw new ExecuteException("不支持图片的方式识别验证码");
                default:
                    logger.warn("不支持的查找元素方式：{}", target.getUsing());
                    throw new ExecuteException("不支持xpath、ocr以外的方式识别验证码");
            }
            if (null != result) {
                return result;
            }
        }
        return result;
    }

    public static String ocrFindCodeArea(IStepTarget target, AppiumDriver<WebElement> driver, IExecuteContext context) {
        String result = null;
        JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class);
        Integer x = value.getInteger("x");
        Integer y = value.getInteger("y");
        Integer screenWidth = value.getInteger("screenWidth");
        Integer screenHeight = value.getInteger("screenHeight");
        Map<String, Object> deviceInfo = context.getDeviceInfo();
        String resolution = (String) deviceInfo.get("resolution");
        Map<String, Object> paramMap = new HashMap<>();
        if (driver instanceof IOSDriver) {
            paramMap.put("platform", "1");
        } else {
            paramMap.put("platform", "0");
        }
        paramMap.put("deviceId", driver.getCapabilities().getCapability("deviceName"));
        paramMap.put("length", value.getInteger("width"));
        paramMap.put("width", value.getInteger("height"));
        paramMap.put("resolution", resolution);
        paramMap.put("x", x);
        paramMap.put("y", y);
        paramMap.put("screenLength", screenWidth);
        paramMap.put("screenWidth", screenHeight);
        logger.info("参数：{}", JSON.toJSONString(paramMap));
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(paramMap);
        String host = driver.getRemoteAddress().getHost();  //上位机地址
        String port = (String) deviceInfo.get("port");   //上位机端口
        String mobileShotAddress = context.getProperty(ConfigPath.MOBILE_SHOT_ADDRESS_KEY);
        String shotScreenAddress = String.format(ConfigPath.HTTP_FORMAT, host, port, mobileShotAddress);
        try {
            result = HttpUtils.doPost(shotScreenAddress, entity, String.class);
        } catch (Exception e) {
            logger.error("截图失败", e);
        }
        return result;
    }

    private static String xpathFindCodeArea(IExecuteContext context, AppiumDriver<WebElement> driver, IStepTarget target, Integer timeout, boolean waitStatus) {
        String result = null;
        JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class);
        String xpath = object.getString(UsingType.Key.SELECTOR_KEY);
        logger.info("拿到的xpath:{}", xpath);
        try {
            driver.manage().timeouts().implicitlyWait(timeout, TimeUnit.SECONDS);
        } catch (NoSuchSessionException e) {
            throw e;
        }
        List<WebElement> elements = null;
        WebElement webElement = null;
        try {
            elements = driver.findElements(By.xpath(xpath));
        } catch (NoSuchSessionException e) {
            throw e;
        }
        logger.info("找到元素{}个", elements.size());
        if (!CollectionUtils.isEmpty(elements)) {   //xpath找到了
            webElement = elements.get(0);
        }
        /*int address = 1;
        Xpath xpathThread = Xpath.build(driver, xpath).isMore(true).setAddress(address).isSwipe("0").setSwipeCount(0).withTimeout(timeout);
        Future<WebElement> submit = findElementService.submit(xpathThread);
        WebElement webElement = null;
        SessionId sessionId = driver.getSessionId();
        logger.debug("当前driver的sessionId为：{}", sessionId);
        try {
            webElement = submit.get(timeout, TimeUnit.SECONDS);
            logger.info("拿到元素：{}", webElement);
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof NoSuchSessionException) {
                logger.warn("driver失效了。。。。");
                NoSuchSessionException nsse = (NoSuchSessionException) cause;
                throw nsse;
            }
        } catch (Exception e) {
            logger.error("xpath找元素出错", e);
            xpathThread.kill();
        }*/
        if (null != webElement) {
            String screenBase64 = null;
            String deviceId = (String) driver.getCapabilities().getCapability("deviceName");
            Map<String, Object> deviceInfo = context.getDeviceInfo();
            String host = driver.getRemoteAddress().getHost();  //上位机地址
            String port = (String) deviceInfo.get("port");   //上位机端口
            String resolution = (String) deviceInfo.get("resolution");  //分辨率
            Map<String, String> screenParam = new HashMap<>();
            screenParam.put("deviceId", deviceId);
            screenParam.put("resolution", resolution);
            if (driver instanceof AndroidDriver) {
                screenParam.put("platform", "0");
            } else if (driver instanceof IOSDriver) {
                screenParam.put("platform", "1");
            }
            String mobileShotAddress = context.getProperty(ConfigPath.MOBILE_SCREEN_SHOT_ADDRESS_KEY);
            HttpEntity<Map> screenEntity = new HttpEntity<>(screenParam, null);
            String shotAllScreenAddress = String.format(ConfigPath.HTTP_FORMAT, host, port, mobileShotAddress);
            screenBase64 = HttpUtils.doPost(shotAllScreenAddress, screenEntity, String.class);
            if (!StringUtils.hasText(screenBase64)) {
                throw new ExecuteException("当前页面截图失败，请确定当前页面是否支持截屏");
            }
            Rectangle rect = webElement.getRect();
            ByteArrayInputStream inputStream = null;
            ByteArrayOutputStream outputStream = null;
            try {
                inputStream = new ByteArrayInputStream(Base64.decode(screenBase64));
                outputStream = new ByteArrayOutputStream();
                ImgUtil.cut(inputStream, outputStream, new java.awt.Rectangle(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()));
                result = Base64.encode(outputStream.toByteArray());
            } catch (Exception e) {
                logger.error("截取验证码区域失败", e);
                throw new ExecuteException("截取验证码区域失败");
            } finally {
                if (null != inputStream) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        logger.error("", e);
                    }
                }
                if (null != outputStream) {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        logger.error("", e);
                    }
                }
            }
        }
        return result;
    }

    public static void inputTextDirect(AppiumDriver<WebElement> driver, String value, boolean clean) {
        if (!StringUtils.hasText(value)) {
            throw new ExecuteException("输入的文本不能为空");
        }

        if (driver instanceof AndroidDriver) {
            //清除文本原有的文本
            if (clean) {
                driver.execute("clearElement", ImmutableMap.of("id", "focusElement"));
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                logger.error("", e);
                return;
            }
            logger.info("android设备输入文本：{}", value);
            driver.execute("sendKeysToElement", ImmutableMap.of("id", "focusElement", "value", new CharSequence[]{value}));
        } else {
            logger.info("ios设备输入文本：{}", value);
            IosUtil.sendKeys(value, 60, driver);
        }
    }

    public static PointMessage findWebElement(IExecuteContext context, AppiumDriver<WebElement> driver, List<IStepTarget> targets, Integer waitTimeout, boolean waitStatus, String swipe, Integer swipeCount) {
        PointMessage point = null;
        try {
            point = findElement(context, driver, targets, waitTimeout, waitStatus, swipe, swipeCount);
        } catch (ExecuteException | EnvironmentException | ParamMistakeException | WebDriverException e) {
            throw e;
        } catch (Exception e) {
            logger.error("查找控件异常", e);
        }
        return point;
    }

    public static boolean isElementExist(IExecuteContext context, AppiumDriver<WebElement> driver, List<IStepTarget> targets, Integer waitTimeout, boolean waitStatus, String swipe, Integer swipeCount) {
        boolean exist = false;
        try {
            exist = doElementExist(context, driver, targets, waitTimeout, waitStatus, swipe, swipeCount);
        } catch (ExecuteException | EnvironmentException | ParamMistakeException | WebDriverException e) {
            throw e;
        } catch (Exception e) {
            logger.error("查找控件异常", e);
        }
        return exist;
    }

    private static boolean doElementExist(IExecuteContext context, AppiumDriver<WebElement> driver, List<IStepTarget> targets, Integer waitTimeout, boolean waitStatus, String swipe, Integer swipeCount) {
        boolean exist = false;
        if (!CollectionUtils.isEmpty(targets)) {
            int size = targets.size();
            int timeOut = waitTimeout / size;
            if (timeOut <= 0) {
                timeOut = 1;
            }
            logger.info("每一个找{}s", timeOut);
            PointMessage point = null;
            for (IStepTarget target : targets) {
                StopWatch stopWatch = new StopWatch();
                stopWatch.start();
                switch (target.getUsing()) {
                    case UsingType.SELECTOR:
                        point = xpathFindElementExist(driver, target, timeOut, swipe, swipeCount);
                        break;
                    case UsingType.OCR:
                        point = ocrFind(target, driver, context, timeOut);
                        break;
                    case UsingType.HZOCR:
                        point = hzOcrFind(target, driver, context, timeOut);
                        break;
                    case UsingType.POINT:
                        point = pointFind(target, driver, context);
                        break;
                    case UsingType.IMAGE:
                        point = imageFind(context, driver, target, timeOut, swipe, swipeCount);
                        break;
                    default:
                        logger.warn("不支持的查找元素方式：{}", target.getUsing());
                        break;
                }
                stopWatch.stop();
                logger.info("{}找了{}s", target.getUsing(), stopWatch.getTotalTimeSeconds());
                if (point != null) {
                    exist = true;
                    Object debugFlag = context.getContextVariable("__isDebug");
                    if (debugFlag != null && !(boolean)debugFlag ) {
                        // 用于添加截图红点
                        CommonUtils.screenShot(context, driver, point);
                    }
                }
            }
        }
        return exist;
    }

    public static PointMessage swipeAndFindTargetElement(PointMessage swipePoint, IExecuteContext context, AppiumDriver<WebElement> driver, List<IStepTarget> targets, Integer waitTimeout, String direction, Integer index) {
        logger.debug("执行滑动查找控件的操作,目标控件：{}", JSON.toJSONString(targets));
        long startTime = System.currentTimeMillis();
        long endTime = System.currentTimeMillis();
        PointMessage targetPoint = null;
        int num = 1;
        do {
            logger.debug("开始第{}次查找", num);
            targetPoint = findWebElementByTargets(context, driver, targets, index);
            logger.debug("第{}次查找的结果：{}", num,JSON.toJSONString(targetPoint));
            if (null == targetPoint) {
                logger.debug("开始第{}次滑屏",num);
                swipeScreenByPoint(swipePoint,driver,direction,context);
            }
            num++;
            endTime = System.currentTimeMillis();
        } while (targetPoint == null && endTime - startTime < waitTimeout * 1000);
        logger.debug("在规定的时间内查找的结果：{}", JSON.toJSONString(targetPoint));
        return targetPoint;
    }

    private static void swipeScreenByPoint(PointMessage swipePoint, AppiumDriver<WebElement> driver, String direction, IExecuteContext context) {
        int x1 = swipePoint.getX();
        int y1 = swipePoint.getY();
        int x2, y2;
        if ("up".equalsIgnoreCase(direction)) {
            x2 = x1;
            y2 = y1 - 500;
        } else if ("down".equalsIgnoreCase(direction)) {
            x2 = x1;
            y2 = y1 + 500;
        } else if ("left".equalsIgnoreCase(direction)) {
            x2 = x1 - 500;
            y2 = y1;
        } else if ("right".equalsIgnoreCase(direction)) {
            x2 = x1 + 500;
            y2 = y1;
        } else {
            throw new ParamMistakeException("滑动方向仅支持：up、down、left、right");
        }
        logger.debug("开始节点x:{} y:{},结束节点x:{},y:{}", x1, y1, x2, y2);
        try {
            new TouchAction(driver).press(PointOption.point(x1, y1))
                    .waitAction(WaitOptions.waitOptions(Duration.ofMillis(2000)))
                    .moveTo(PointOption.point(x2, y2))
                    .release().perform();
        } catch (WebDriverException e) {
            if (e instanceof NoSuchSessionException) {
                throw e;
            } else {
                logger.warn("driver 抽风了。。。。。,重新来一次");
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException interruptedException) {

                }
                new TouchAction(driver).press(PointOption.point(x1, y1))
                        .waitAction(WaitOptions.waitOptions(Duration.ofMillis(2000)))
                        .moveTo(PointOption.point(x2, y2))
                        .release().perform();
            }
        }
    }

    private static PointMessage findWebElementByTargets(IExecuteContext context, AppiumDriver<WebElement> driver, List<IStepTarget> targets, Integer index) {
        PointMessage targetPoint = null;
        for (IStepTarget target : targets) {
            switch (target.getUsing()) {
                case UsingType.SELECTOR:
                    targetPoint = findElementByXpath(driver, target, index);
                    if (null != targetPoint) {
                        return targetPoint;
                    }
                    break;
                case UsingType.OCR:
                    targetPoint = findElementByOcr(target, driver, context);
                    if (null != targetPoint) {
                        return targetPoint;
                    }
                    break;
                case UsingType.POINT:
                    targetPoint = pointFind(target, driver, context);
                    if (null != targetPoint) {
                        return targetPoint;
                    }
                    break;
                case UsingType.IMAGE:
                    targetPoint = findElementByImage(context, driver, target);
                    if (null != targetPoint) {
                        return targetPoint;
                    }
                    break;
                default:
                    logger.warn("不支持的查找元素方式：{}", target.getUsing());
                    break;
            }
        }
        return null;
    }

    private static PointMessage findElementByImage(IExecuteContext context, AppiumDriver<WebElement> driver, IStepTarget target) {
        PointMessage point = null;
        String serverAddress = context.getProperty(ConfigPath.SERVER_URL);  //图片下载地址
        String picDownloadAddress = context.getProperty(ConfigPath.DOWNLOAD_PIC_ADDRESS);  //图片下载地址
        String findPicAddress = context.getProperty(ConfigPath.FIND_PIC_ADDRESS_KEY);  //找图片地址
        String screenShotAddress = context.getProperty(ConfigPath.MOBILE_SCREEN_SHOT_ADDRESS_KEY);  //截全屏地址
        JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class);
//        Float faultRate = value.getFloat("faultRate");
        Float faultRate = 0.8f;
        String imageUrl = value.getString("imgUri");
        String fileName = imageUrl.substring(imageUrl.lastIndexOf("_") + 1);
        Map<String, Object> deviceInfo = context.getDeviceInfo();
        String host = driver.getRemoteAddress().getHost();  //上位机地址
        String port = (String) deviceInfo.get("port");   //上位机端口
        String resolution = (String) deviceInfo.get("resolution");  //分辨率
        String url = serverAddress + picDownloadAddress + imageUrl;
        String shotAllScreenAddress = String.format(ConfigPath.HTTP_FORMAT, host, port, screenShotAddress);
        HttpUtils.downloadFileToLocal(url, System.getProperty("user.dir") + "/tempPic", fileName);
        File file = new File(System.getProperty("user.dir") + "/tempPic/" + fileName);
        String base64Img = Base64.encode(file);
        Map<String, BigDecimal> area = findAreaByImage(shotAllScreenAddress, findPicAddress, driver, 3, base64Img, faultRate, resolution, "0", 0);
        if (null != area) {
            try {
                byte[] bytes = new BASE64Decoder().decodeBuffer(base64Img);
                BufferedImage read = ImageIO.read(new ByteArrayInputStream(bytes));
                int width = read.getWidth();
                int height = read.getHeight();
                point = new PointMessage(area.get("x").intValue() + width / 2, area.get("y").intValue() + height / 2, true);
            } catch (IOException e) {
                logger.error("转换base64图片失败：{}", e);
            }
        }
        return point;
    }

    private static PointMessage findElementByOcr(IStepTarget target, AppiumDriver<WebElement> driver, IExecuteContext context) {
        String ocrAddress = context.getProperty(ConfigPath.OCR_ADDRESS_KEY);
        String mobileShotAddress = context.getProperty(ConfigPath.MOBILE_SHOT_ADDRESS_KEY);
        PointMessage point = null;
        JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class);
        Integer x = value.getInteger("x");
        Integer y = value.getInteger("y");
        Integer screenWidth = value.getInteger("screenWidth");
        Integer screenHeight = value.getInteger("screenHeight");
        Map<String, Object> deviceInfo = context.getDeviceInfo();
        String resolution = (String) deviceInfo.get("resolution");
        boolean[] rotation = new boolean[1];  //默认是正常手机的宽高
        rotation[0] = true;
        if (screenWidth > screenHeight) {
            rotation[0] = false;
        }
        Map<String, Object> paramMap = new HashMap<>();
        if (driver instanceof IOSDriver) {
            paramMap.put("platform", "1");
        } else {
            paramMap.put("platform", "0");
        }
        paramMap.put("deviceId", driver.getCapabilities().getCapability("deviceName"));
        paramMap.put("length", value.getInteger("width"));
        paramMap.put("width", value.getInteger("height"));
        paramMap.put("resolution", resolution);
        paramMap.put("x", x);
        paramMap.put("y", y);
        paramMap.put("screenLength", screenWidth);
        paramMap.put("screenWidth", screenHeight);
        logger.info("参数：{}", JSON.toJSONString(paramMap));
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(paramMap);
        String host = driver.getRemoteAddress().getHost();  //上位机地址
        String port = (String) deviceInfo.get("port");   //上位机端口
        String shotScreenAddress = String.format(ConfigPath.HTTP_FORMAT, host, port, mobileShotAddress);
        String result = null;
        try {
            result = HttpUtils.doPost(shotScreenAddress, entity, String.class);
        } catch (Exception e) {
            logger.error("截图失败", e);
        }
        if (null == result) {
            return point;
        }
        boolean useNewOcr = Boolean.parseBoolean(context.getProperty(ConfigPath.USE_NEW_OCR));
        if (useNewOcr) {
            ocrAddress = context.getProperty(ConfigPath.OCR_GET_ALL_TEXT_NUM_KEY);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            String ocrText = value.getString("ocrText");
            Integer index = value.getInteger("index");
            Map<String, Object> ocrParamMap = new HashMap<>();
            ocrParamMap.put("img_base64", result);
            if (!StringUtils.hasText(ocrText)) {
                throw new ExecuteException("点击的文本不能为空！");
            }
            ocrParamMap.put("targets", ocrText);
            HttpEntity<Map<String, Object>> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
            List ocrResultList = null;
            try {
                logger.info("传参：{}", JSON.toJSONString(ocrParamMap));
                ocrResultList = HttpUtils.doPost(ocrAddress, ocrEntity, List.class);
                logger.info("得到的ocr结果：{}", ocrResultList);
            } catch (Exception e) {
                logger.error("ocr失败", e);
            }
            if (!CollectionUtils.isEmpty(ocrResultList)) {
                String[] split = resolution.split("\\*");
                int width = Integer.parseInt(split[0]);
                int height = Integer.parseInt(split[1]);
                int realityWidth = width;
                int realityHeight = height;
                if (rotation[0]) {   //正常
                    if (width > height) {  //宽大于高，调换
                        realityWidth = height;
                        realityHeight = width;
                    }
                } else {  //不正常，横屏
                    if (width < height) {  //宽小于高，调换
                        realityWidth = height;
                        realityHeight = width;
                    }
                }
                Double realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
                Double realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
                Object all = ocrResultList.get(0);
                JSONObject jsonObject = JSONObject.parseObject(JSONArray.toJSONString(all));
                //todo：如果传了;是多个文本一起找，暂时只考虑一个文本
                JSONArray data = jsonObject.getJSONArray(ocrText);   //所有坐标
                if (!CollectionUtils.isEmpty(data)) {
                    if (data.size() == 1) {
                        JSONObject itemPoint = JSONObject.parseObject(JSONObject.toJSONString(data.get(0)));
                        point = new PointMessage(itemPoint.getInteger("x") + (itemPoint.getInteger("w") / 2) + realityX.intValue(),
                                itemPoint.getInteger("y") + (itemPoint.getInteger("h") / 2) + realityY.intValue(), true);
                    } else {
                        if (index > 0) {
                            if (index <= data.size()) {
                                point = sortPointAndGetIndexPoint(data, index);
                            } else {
                                throw new ExecuteException(String.format("选择元素的位置大于找到元素的个数，找到元素%s个", data.size()));
                            }
                        }
                    }
                }
            }
        } else {
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            Map<String, Object> ocrParamMap = new HashMap<>();
            ocrParamMap.put("base64Image", result);
            ocrParamMap.put("CaseSensitive", (null == value.getBoolean("CaseSensitive")) ? false : value.getBoolean("CaseSensitive"));
            if (!StringUtils.hasText(value.getString("ocrText"))) {
                throw new ExecuteException("ocr查找的文本不能为空！");
            }
            ocrParamMap.put("ocrText", value.getString("ocrText"));
            HttpEntity<Map<String, Object>> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
            JSONObject ocrResultMap = null;
            try {
                logger.info("传参：{}", JSON.toJSONString(ocrParamMap));
                ocrResultMap = HttpUtils.doPost(ocrAddress, ocrEntity, JSONObject.class);
                logger.info("得到的ocr结果：{}", ocrResultMap);
            } catch (Exception e) {
                logger.error("ocr失败", e);
            }
            if (null != ocrResultMap) {
                Boolean success = ocrResultMap.getBoolean("success");
                if (success) {
                    //转换比例
                    String[] split = resolution.split("\\*");
                    int width = Integer.parseInt(split[0]);
                    int height = Integer.parseInt(split[1]);

                    int realityWidth = width;
                    int realityHeight = height;
                    if (width > height) {  //宽高反了的情况，调换
                        realityWidth = height;
                        realityHeight = width;
                    }
                    Double realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
                    Double realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
                    JSONArray data = ocrResultMap.getJSONArray("data");
                    Integer index = value.getInteger("index");
                    JSONObject pointMap = null;
                    if (data != null && data.size() == 1) {   //只找到一个，直接返回
                        pointMap = data.getJSONObject(0);
                    } else if (data != null && data.size() > 1) {   //找到多个，选择用户选择的第几个
                        if (index > 0) {
                            if (index <= data.size()) {
                                pointMap = data.getJSONObject(index - 1);
                            }
                        }
                    }
                    if (null != pointMap) {
                        point = new PointMessage(realityX.intValue() + pointMap.getInteger("x"), realityY.intValue() + pointMap.getInteger("y"), true);
                    }
                }
            }
        }
        return point;
    }

    private static PointMessage findElementByXpath(AppiumDriver<WebElement> driver, IStepTarget target, Integer index) {
        PointMessage point = null;
        JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class);
        String xpath = object.getString(UsingType.Key.SELECTOR_KEY);
        logger.info("拿到的xpath:{}", xpath);
        try {
            driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        } catch (NoSuchSessionException e) {
            throw e;
        }
        WebElement webElement = null;

        List<WebElement> elements = null;
        try {
            elements = driver.findElements(By.xpath(xpath));
        } catch (NoSuchSessionException e) {
            throw e;
        }
        logger.info("当前页面找到元素{}个", elements.size());
        if (!CollectionUtils.isEmpty(elements)) {   //xpath找到了
            if (elements.size() == 1) {
                webElement = elements.get(0);
            } else {   //找到多个时，暂时还是返回第一个
                logger.info("用xpath在当前页面找到{}元素,拿第{}个元素.......................", elements.size(), index);
                if (index <= elements.size()) {
                    webElement = elements.get(index - 1);
                }
            }
        } else {  //xpath没找到
            logger.info("用xpath在当前页面没有找到元素.......................");
        }
        if (null != webElement) {
            Rectangle rect = webElement.getRect();
            int x = rect.getX();
            int y = rect.getY();
            int width = rect.getWidth();
            int height = rect.getHeight();
            point = new PointMessage(x + width / 2, y + height / 2, false);
        }
        return point;
    }

    private static PointMessage xpathFindElementExist(AppiumDriver<WebElement> driver, IStepTarget target, int timeOut, String swipe, Integer swipeCount) {
        PointMessage point = null;
        JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class);
        String xpath = object.getString(UsingType.Key.SELECTOR_KEY);
        logger.info("拿到的xpath:{}", xpath);
        Integer perPageTimeout = timeOut;
        Integer swipeNum = swipeCount;
        if ((swipeCount <= 0 || swipeCount > 5)/*限定滑屏查找不超过5次*/ || "0".equals(swipe)  /*不滑屏*/) {
            swipeNum = 0;
        }
        if (swipeNum > 0) {
            perPageTimeout = timeOut / swipeCount;
        }
        try {
            driver.manage().timeouts().implicitlyWait(perPageTimeout, TimeUnit.SECONDS);
        } catch (NoSuchSessionException e) {
            throw e;
        }
        WebElement webElement = getWebElement(driver, swipe, xpath, swipeNum);
        Rectangle rect = null;
        try {
            rect = webElement.getRect();
        } catch (StaleElementReferenceException e) {
            logger.warn("页面发生变化，重新查找元素", e);
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException ex) {
                logger.error("在查找元素等待时被打断", ex);
            }
            logger.info("再次查找当前页面的元素");
            WebElement we = getWebElement(driver, swipe, xpath, swipeNum);
            if (null != we) {
                rect = we.getRect();
            }
        }
        if (null != rect) {
            int x = rect.getX();
            int y = rect.getY();
            int width = rect.getWidth();
            int height = rect.getHeight();
            point = new PointMessage(x + width / 2, y + height / 2, false);
        }
        return point;
    }

    public static PointMessage findTextByOcr(IExecuteContext context, AppiumDriver<WebElement> driver, String value, Integer index) {
        PointMessage point = null;
        String ocrAddress = context.getProperty(ConfigPath.OCR_GET_ALL_TEXT_NUM_KEY);
        String mobileShotAddress = context.getProperty(ConfigPath.MOBILE_SCREEN_SHOT_ADDRESS_KEY);
        Map<String, Object> deviceInfo = context.getDeviceInfo();
        String resolution = (String) deviceInfo.get("resolution");
        Map<String, Object> paramMap = new HashMap<>();
        if (driver instanceof IOSDriver) {
            paramMap.put("platform", "1");
        } else {
            paramMap.put("platform", "0");
        }
        paramMap.put("deviceId", driver.getCapabilities().getCapability("deviceName"));
        paramMap.put("resolution", resolution);
        logger.info("参数：{}", JSON.toJSONString(paramMap));
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(paramMap);
        String host = driver.getRemoteAddress().getHost();  //上位机地址
        String port = (String) deviceInfo.get("port");   //上位机端口
        String shotScreenAddress = String.format(ConfigPath.HTTP_FORMAT, host, port, mobileShotAddress);
        String result = null;
        try {
            result = HttpUtils.doPost(shotScreenAddress, entity, String.class);
        } catch (Exception e) {
            logger.error("截图失败", e);
        }
        if (null == result) {
            return null;
        }
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        Map<String, Object> ocrParamMap = new HashMap<>();
        ocrParamMap.put("img_base64", result);
        if (!StringUtils.hasText(value)) {
            throw new ExecuteException("点击的文本不能为空！");
        }
        ocrParamMap.put("targets", value);
        HttpEntity<Map<String, Object>> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
        List ocrResultList = null;
        try {
            logger.info("传参：{}", JSON.toJSONString(ocrParamMap));
            ocrResultList = HttpUtils.doPost(ocrAddress, ocrEntity, List.class);
            logger.info("得到的ocr结果：{}", ocrResultList);
        } catch (Exception e) {
            logger.error("ocr失败", e);
        }
        if (!CollectionUtils.isEmpty(ocrResultList)) {
            Object all = ocrResultList.get(0);
            logger.info("获取到的识别内容：{}",JSONObject.toJSONString(all));
            JSONObject jsonObject = null;
            if (all instanceof String) {
                logger.info("ocr结果为String获取到的识别内容：{}",all.toString());
                jsonObject = JSONObject.parseObject((String)all);
            } else {
                jsonObject = JSONObject.parseObject(JSONObject.toJSONString(all));
            }
            //todo：如果传了;是多个文本一起找，暂时只考虑一个文本
            JSONArray data = jsonObject.getJSONArray(value);   //所有坐标
            if (!CollectionUtils.isEmpty(data)) {
                if (data.size() == 1) {
                    JSONObject itemPoint = JSONObject.parseObject(JSONObject.toJSONString(data.get(0)));
                    point = new PointMessage(itemPoint.getInteger("x") + (itemPoint.getInteger("w") / 2), itemPoint.getInteger("y") + (itemPoint.getInteger("h") / 2), true);
                } else {
                    if (index > 0) {
                        if (index <= data.size()) {
                            point = sortPointAndGetIndexPoint(data, index);
                        } else {
                            throw new ExecuteException(String.format("选择元素的位置大于找到元素的个数，找到元素%s个", data.size()));
                        }
                    }
                }
            }
        }
        return point;
    }

    private static PointMessage sortPointAndGetIndexPoint(JSONArray data, Integer index) {
        List<JSONObject> list = new ArrayList<>();
        List<PointMessage> allPoint = new ArrayList<>();
        //y排好
        data.stream().sorted(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                JSONObject one = JSONObject.parseObject(JSONObject.toJSONString(o1));
                JSONObject two = JSONObject.parseObject(JSONObject.toJSONString(o2));
                return one.getIntValue("y") - two.getIntValue("y");
            }
        }).forEach(item -> list.add(JSONObject.parseObject(JSONObject.toJSONString(item))));
        JSONObject[][] plane = new JSONObject[list.size()][list.size()];
        int yIndex = 0;
        int xIndex = 0;
        for (int j = 0; j < list.size()-1; j++) {
            plane[xIndex][yIndex] = list.get(j);
            yIndex++;
            JSONObject pre = list.get(j);
            JSONObject suf = list.get(j+1);
            if (suf.getIntValue("y") - pre.getIntValue("y") > 10) {
                yIndex = 0;
                xIndex++;
            }
        }
        plane[xIndex][yIndex] = list.get(list.size() - 1);  //最后一个
        //对每一行排序
        for (int k = 0; k <= xIndex; k++) {
            List<JSONObject> lineList = new ArrayList<>();
            for (int m = 0; m < list.size(); m++) {
                if (null != plane[k][m]) {
                    lineList.add(plane[k][m]);
                } else {
                    break;
                }
            }
            lineList.stream().sorted(new Comparator<JSONObject>() {
                @Override
                public int compare(JSONObject o1, JSONObject o2) {
                    return o1.getIntValue("x") - o2.getIntValue("x");
                }
            }).forEach(item -> {
                PointMessage pointMessage = new PointMessage(item.getInteger("x") + (item.getInteger("w") / 2), item.getInteger("y") + (item.getInteger("h") / 2), true);
                allPoint.add(pointMessage);
            });
        }
        return allPoint.get(index - 1);
    }

}
