package net.northking.cctp.element.mobile.thread;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
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.offset.PointOption;
import net.northking.cctp.element.core.DeviceDriver;
import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.IStepTarget;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.element.mobile.constants.ConfigPath;
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.phoneUtils.IosUtil;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchSessionException;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.concurrent.Callable;

public class InputPasswordThreadByOcrThread implements Callable<Boolean> {

    private Logger logger = LoggerFactory.getLogger(InputPasswordThreadByOcrThread.class);

    private String password;
    private IExecuteContext context;
    private List<IStepTarget> targets;
    private IStepTarget target;
    private AppiumDriver<WebElement> driver;
    private Float preExecuteWait;
    private Float sufExecuteWait;
    private Rectangle elementRec;
    private Map<String, JSONObject> firstMap;
    private Map<String, JSONObject> secondMap;
    private Map<String, JSONObject> thirdMap;
    private Map<String, Map<String, JSONObject>> totalMap = new HashMap<>();
    private List<Map<String, JSONObject>> list;
    private int index = 1;
    private String lastKeyBoard;

    private String preFlag = "0";    //0-小写字母，1-数字，2-符号,3-大写字母


    public InputPasswordThreadByOcrThread(IExecuteContext context, List<IStepTarget> targets, DeviceDriver deviceDriver, Float preExecuteWait, Float sufExecuteWait, String password) {
        this.password = password;
        this.targets = targets;
        this.context = context;
        this.driver = (AppiumDriver<WebElement>) deviceDriver;
        this.preExecuteWait = preExecuteWait;
        this.sufExecuteWait = sufExecuteWait;
        firstMap = new HashMap<>();
        secondMap = new HashMap<>();
        thirdMap = new HashMap<>();
        list = new ArrayList<>(3);
        list.add(firstMap);
        list.add(secondMap);
        list.add(thirdMap);

    }

    @Override
    public Boolean call() throws Exception {
        CommonUtils.handlePreExecuteWait(preExecuteWait);
        if (!StringUtils.hasText(password)) {
            throw new ExecuteException("输入的密码不能为空");
        }
        String ocrArea = getKeyBoardArea();
        if (null == ocrArea) {
            throw new ExecuteException("未找到键盘区域");
        }
        lastKeyBoard = ocrArea;
        getKeyBoardInfo(ocrArea);
        char[] passwordChars = password.toCharArray();
        for (char passwordChar : passwordChars) {
            inputPasswordChar(passwordChar);
        }
        CommonUtils.handleSufExecuteWait(sufExecuteWait);
        return true;
    }

    private String getKeyBoardArea() {
        String ocrArea = null;
        for (IStepTarget item : targets) {
            target = item;
            if (UsingType.OCR.equalsIgnoreCase(item.getUsing())) {
                ocrArea = getOcrArea(item.getValue());
                if (null != ocrArea) {
                    break;
                }
            } else if (UsingType.SELECTOR.equalsIgnoreCase(item.getUsing())) {
                JSONObject object = JSON.parseObject(item.getValue(), JSONObject.class);
                String xpath = object.getString(UsingType.Key.SELECTOR_KEY);
                logger.info("拿到的xpath:{}", xpath);
                ocrArea = findOcrAreaByXpath(xpath);
                if (null != ocrArea) {
                    break;
                }
            }
        }
        return ocrArea;
    }

    private void getKeyBoardInfo(String ocrArea) {
        Map<String, JSONObject> itemMap = new HashMap<>();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Type", "application/json");
        Map<String, Object> paramMap = new HashMap<>();
        logger.debug("第{}次进来键盘base64：{}",index,ocrArea);
        paramMap.put("img_base64", ocrArea);
        HttpEntity<Map> entity = new HttpEntity<>(paramMap, httpHeaders);
        String keyboardAddress = context.getProperty(ConfigPath.OCR_GET_KEYBOARD_KEY);
        List result = null;
        try {
            result = HttpUtils.doPost(keyboardAddress, entity, List.class);
            boolean numKeyboard = true;
            for (Object o : JSONObject.parseArray(JSONArray.toJSONString(result.get(0)))) {
                JSONArray parseArray = JSONObject.parseArray(JSON.toJSONString(o));
                logger.debug("第{}次进来识别键盘结果：{}",index,parseArray);
                if (parseArray.size() < 5) {
                    throw new ExecuteException("键盘识别失败，ocr返回结果错误");
                }
                // orc返回数组顺序 x y w h
                String name = (String) parseArray.get(0);
                JSONObject item = new JSONObject();
                item.put("name", name);
                item.put("x", parseArray.get(1));
                item.put("y", parseArray.get(2));
                item.put("w", parseArray.get(3));
                item.put("h", parseArray.get(4));
                if (name.equals("shift")) {
                    logger.debug("第{}次进来是小写字母键盘........", index);
                    preFlag = "0";
                    numKeyboard = false;
                } else if (name.equals("highshift")) {
                    logger.debug("第{}次进来是大写字母键盘........", index);
                    preFlag = "3";
                    numKeyboard = false;
                } else if (name.equals("@")) {
                    logger.debug("第{}次进来是字符键盘...........", index);
                    preFlag = "2";
                    numKeyboard = false;
                }
                itemMap.put(name, item);
            }
            if (numKeyboard) {
                logger.debug("第{}次进来是数字界面...........", index);
                preFlag = "1";
            }
            logger.info("第{}次进来的界面标志为：{}", index, preFlag);
            totalMap.put(preFlag, itemMap);
            index++;
        } catch (Exception e) {
            logger.error("识别键盘失败", e);
            throw new ExecuteException("键盘识别失败");
        }
    }

    private void inputPasswordChar(char passwordChar) {
        logger.info("密码字符：{}", passwordChar);
        boolean isNumber = Character.isDigit(passwordChar);
        if (isNumber) {   //数字
            if (!"1".equals(preFlag)) {  //当前不在数字页面
                logger.info("切换数字页面，密码字符：{}", passwordChar);
                exchangePage("num");
                preFlag = "1";
            }
            Map<String, JSONObject> numMap = totalMap.get(preFlag);
            if (null == numMap){  //还没有对应的键盘页面
                getKeyBoardInfo(lastKeyBoard);
                numMap = totalMap.get(preFlag);
            }
            JSONObject item = numMap.get(String.valueOf(passwordChar));
            click(item);
            return;
        }
        boolean letter = Character.isLetter(passwordChar);
        if (letter) {  //字母
            if (!"0".equals(preFlag) && !"3".equals(preFlag)) {  //既不是大写字母也不是小写字母
                exchangePage("letter");
                preFlag = determinePage();
            }
            Map<String, JSONObject> letterMap = totalMap.get(preFlag);
            if (null == letterMap){  //还没有对应的键盘页面
                getKeyBoardInfo(lastKeyBoard);
                letterMap = totalMap.get(preFlag);
            }
            boolean lowerCase = Character.isLowerCase(passwordChar);
            if (!lowerCase) {   //大写
                if ("3".equals(preFlag)) {  //当前处于大写键盘界面
                    if (totalMap.get("3") == null) {
                        getKeyBoardInfo(getOcrArea(target.getValue()));
                    }
                    JSONObject item = letterMap.get(String.valueOf(passwordChar));
                    click(item);  //点字母
                } else {//当前处于小写键盘界面
                    JSONObject shiftItem = letterMap.get("shift");
                    click(shiftItem);  //点大写
                    if (totalMap.get("3") == null) {
                        getKeyBoardInfo(getOcrArea(target.getValue()));
                    }
                    JSONObject item = totalMap.get("3").get(String.valueOf(passwordChar));
                    click(item);  //点字母
                    preFlag = "0";
                }
            } else {  //小写
                if ("0".equals(preFlag)) {
                    JSONObject item = totalMap.get("0").get(String.valueOf(passwordChar));
                    click(item);  //点字母
                } else {
                    JSONObject shiftItem = totalMap.get("1").get("highshift");
                    click(shiftItem);  //点大写
                    preFlag = determinePage();
                    if ("3".equals(preFlag)){//还处于大写
                        click(shiftItem);  //点大写
                    }
                    JSONObject item = letterMap.get(String.valueOf(passwordChar));
                    click(item);  //点字母
                }
            }
        } else {  //特殊字符
            if (!"2".equals(preFlag)) {    //不是特殊字符，切换到特殊字符
                exchangePage("character");
                preFlag = "2";//当前页面是字符界面
            }
            Map<String, JSONObject> characterMap = totalMap.get(preFlag);
            if (null == characterMap){  //还没有对应的键盘页面
                getKeyBoardInfo(lastKeyBoard);
                characterMap = totalMap.get(preFlag);
            }
            JSONObject item = characterMap.get(String.valueOf(passwordChar));
            click(item);
        }
    }

    private String determinePage() {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Type", "application/json");
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("img_base64", lastKeyBoard);
        HttpEntity<Map> entity = new HttpEntity<>(paramMap, httpHeaders);
        String keyboardAddress = context.getProperty(ConfigPath.OCR_GET_KEYBOARD_KEY);
        List result = null;
        try {
            result = HttpUtils.doPost(keyboardAddress, entity, List.class);
            for (Object o : JSONObject.parseArray(JSONArray.toJSONString(result.get(0)))) {
                JSONArray parseArray = JSONObject.parseArray(JSON.toJSONString(o));
                if (parseArray.size() < 5) {
                    throw new ExecuteException("键盘识别失败，ocr返回结果错误");
                }
                String name = (String) parseArray.get(0);
                if (name.equals("shift")) {
                    logger.debug("第{}次进来是小写字母键盘........", index);
                    return "0";
                } else if (name.equals("highshift")) {
                    logger.debug("第{}次进来是大写字母键盘........", index);
                    return "3";
                }
            }
        } catch (Exception e) {
            logger.error("识别键盘失败", e);
        }
        return "0";
    }

    private void exchangePage(String flag) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Type", "application/json");
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("img_base64", lastKeyBoard);
        HttpEntity<Map> entity = new HttpEntity<>(paramMap, httpHeaders);
        String keyboardAddress = context.getProperty(ConfigPath.OCR_GET_KEYBOARD_KEY);
        List result = null;
        try {
            result = HttpUtils.doPost(keyboardAddress, entity, List.class);
            for (Object o : JSONObject.parseArray(JSONArray.toJSONString(result.get(0)))) {
                JSONArray parseArray = JSONObject.parseArray(JSON.toJSONString(o));
                if (parseArray.size() < 5) {
                    throw new ExecuteException("键盘识别失败，ocr返回结果错误");
                }
                String name = (String) parseArray.get(0);
                if (name.equals(flag)) {
                    logger.info("切换页面flag：{}", flag);
                    JSONObject item = new JSONObject();
                    item.put("name",parseArray.get(0));
                    item.put("x", parseArray.get(1));
                    item.put("y", parseArray.get(2));
                    item.put("w", parseArray.get(3));
                    item.put("h", parseArray.get(4));
                    click(item);
                    break;
                }
            }
        } catch (Exception e) {
            logger.error("识别键盘失败", e);
        }
        String ocrArea = getKeyBoardArea();
        if (null != ocrArea) {
            lastKeyBoard = ocrArea;
        }
    }

    private String findOcrAreaByXpath(String xpath) {
        String result = null;
        if (null == elementRec) {  //第一次进去
            try {
                WebElement element = driver.findElement(By.xpath(xpath));
                if (null != element) {
                    elementRec = element.getRect();
                }
            } catch (NoSuchSessionException e) {
                throw e;
            }
        }
        if (null != elementRec) {
            result = getOcrAreaByRec();
        }
        return result;
    }

    private String getOcrAreaByRec() {
        Map<String, Object> deviceInfo = context.getDeviceInfo();
        String shotAllScreenAddress = context.getProperty(ConfigPath.MOBILE_SCREEN_SHOT_ADDRESS_KEY);
        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, shotAllScreenAddress);
        String result = null;
        try {
            result = HttpUtils.doPost(shotScreenAddress, entity, String.class);
        } catch (Exception e) {
            logger.error("截图失败", e);
        }
        if (null != result) {
            byte[] imageBytes = Base64.getDecoder().decode(result);
            try {
                BufferedImage imageBuffer = ImageIO.read(new ByteArrayInputStream(imageBytes));
                BufferedImage subImageBuffer = imageBuffer.getSubimage(elementRec.getX(), elementRec.getY(), elementRec.getWidth(), elementRec.getHeight());
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                ImageIO.write(subImageBuffer, "png", outputStream);
                return Base64.getEncoder().encodeToString(outputStream.toByteArray());
            } catch (IOException e) {
                logger.warn("读取图片数据失败");
            }

        }
        return null;
    }


    private String getOcrArea(String ocrValue) {
        String mobileShotAddress = context.getProperty(ConfigPath.MOBILE_SHOT_ADDRESS_KEY);
        JSONObject value = JSON.parseObject(ocrValue, 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 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);
        }
        return result;
    }

    private void click(JSONObject item) {
        if (null != item) {
            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;
            boolean[] rotation = new boolean[1];  //默认是正常手机的宽高
            rotation[0] = true;
            JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class);
            Integer screenWidth = value.getInteger("screenWidth");
            Integer screenHeight = value.getInteger("screenHeight");
            Integer x = value.getInteger("x");
            Integer y = value.getInteger("y");
            if (screenWidth > screenHeight) {
                rotation[0] = false;
            }
            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;

            PointMessage point = new PointMessage(realityX.intValue() + (item.getIntValue("x") + item.getIntValue("w") / 2),
                    realityY.intValue() + (item.getIntValue("y") + item.getIntValue("h") / 2), true);
            Double x2 = new Integer(point.getX()).doubleValue();
            Double y2 = new Integer(point.getY()).doubleValue();

            if (point.isExchange()) {
                if (driver instanceof IOSDriver) {
                    Double scale = IosUtil.getWindowsSize(driver, resolution);
                    x2 = x2 / scale;
                    y2 = y2 / scale;
                }
            }

            logger.info("未偏移前点击坐标位置，x2:{},y2:{}", x2, y2);
            logger.info("偏移x:{},y:{}", point.getxMove(), point.getyMove());
            x2 = x2 + point.getxMove();
            y2 = y2 + point.getyMove();
            try {
                logger.info("点击字符：{}", item.get("name"));
                logger.info("实际点击坐标位置，x2:{},y2:{}", x2, y2);
                try {
                    Thread.sleep(300);
                } catch (Exception exception) {
                    logger.error(exception.getMessage());
                }
                new TouchAction<>(driver).tap(PointOption.point(x2.intValue(), y2.intValue())).perform();  //执行点击
            } catch (NoSuchSessionException e) {
                logger.warn("driver失效了",e);
                throw e;
            }
        }
    }
}
